1 describe('Native template engine', {
2 before_each: function () {
3 ko.setTemplateEngine(new ko.nativeTemplateEngine());
5 function ensureNodeExistsAndIsEmpty(id, tagName, type) {
6 var existingNode = document.getElementById(id);
7 if (existingNode != null)
8 existingNode.parentNode.removeChild(existingNode);
9 var resultNode = document.createElement(tagName || "div");
12 resultNode.setAttribute("type", type);
13 document.body.appendChild(resultNode);
17 window.testDivTemplate = ensureNodeExistsAndIsEmpty("testDivTemplate");
18 window.testScriptTemplate = ensureNodeExistsAndIsEmpty("testScriptTemplate", "script", "text/html");
19 window.testTextAreaTemplate = ensureNodeExistsAndIsEmpty("testTextAreaTemplate", "textarea");
20 window.templateOutput = ensureNodeExistsAndIsEmpty("templateOutput");
23 'Named template can display static content from regular DOM element': function () {
24 window.testDivTemplate.innerHTML = "this is some static content";
25 ko.renderTemplate("testDivTemplate", null, null, window.templateOutput);
26 value_of(window.templateOutput).should_contain_html("this is some static content");
29 'Named template can fetch template from regular DOM element and data-bind on results': function () {
30 window.testDivTemplate.innerHTML = "name: <div data-bind='text: name'></div>";
31 ko.renderTemplate("testDivTemplate", { name: 'bert' }, null, window.templateOutput);
32 value_of(window.templateOutput).should_contain_html("name: <div data-bind=\"text: name\">bert</div>");
35 'Named template can fetch template from <script> elements and data-bind on results': function () {
36 window.testScriptTemplate.text = "name: <div data-bind='text: name'></div>";
37 ko.renderTemplate("testScriptTemplate", { name: 'bert' }, null, window.templateOutput);
38 value_of(window.templateOutput).should_contain_html("name: <div data-bind=\"text: name\">bert</div>");
41 'Named template can fetch template from <textarea> elements and data-bind on results': function () {
42 var prop = (typeof window.testTextAreaTemplate.innerText !== "undefined") ? "innerText" : "textContent";
43 window.testTextAreaTemplate[prop] = "name: <div data-bind='text: name'></div>";
44 ko.renderTemplate("testTextAreaTemplate", { name: 'bert' }, null, window.templateOutput);
45 value_of(window.templateOutput).should_contain_html("name: <div data-bind=\"text: name\">bert</div>");
48 'Anonymous template can display static content': function () {
49 new ko.templateSources.anonymousTemplate(window.templateOutput).text("this is some static content");
50 window.templateOutput.innerHTML = "irrelevant initial content";
51 ko.renderTemplate(window.templateOutput, null, null, window.templateOutput);
52 value_of(window.templateOutput).should_contain_html("this is some static content");
55 'Anonymous template can data-bind on results': function () {
56 new ko.templateSources.anonymousTemplate(window.templateOutput).text("name: <div data-bind='text: name'></div>");
57 window.templateOutput.innerHTML = "irrelevant initial content";
58 ko.renderTemplate(window.templateOutput, { name: 'bert' }, null, window.templateOutput);
59 value_of(window.templateOutput).should_contain_html("name: <div data-bind=\"text: name\">bert</div>");
62 'Anonymous templates can be supplied by not giving a template name': function() {
63 window.testDivTemplate.innerHTML = "<div data-bind='template: { data: someItem }'>Value: <span data-bind='text: val'></span></div>"
66 someItem: { val: 'abc' }
68 ko.applyBindings(viewModel, window.testDivTemplate);
70 value_of(window.testDivTemplate.childNodes[0]).should_contain_text("Value: abc");
73 'Anonymous templates work in conjunction with foreach': function() {
74 window.testDivTemplate.innerHTML = "<div data-bind='template: { foreach: myItems }'><b>Item: <span data-bind='text: itemProp'></span></b></div>";
75 var myItems = ko.observableArray([{ itemProp: 'Alpha' }, { itemProp: 'Beta' }, { itemProp: 'Gamma' }]);
76 ko.applyBindings({ myItems: myItems }, window.testDivTemplate);
78 value_of(window.testDivTemplate.childNodes[0].childNodes[0]).should_contain_text("Item: Alpha");
79 value_of(window.testDivTemplate.childNodes[0].childNodes[1]).should_contain_text("Item: Beta");
80 value_of(window.testDivTemplate.childNodes[0].childNodes[2]).should_contain_text("Item: Gamma");
82 // Can cause re-rendering
83 myItems.push({ itemProp: 'Pushed' });
84 value_of(window.testDivTemplate.childNodes[0].childNodes[0]).should_contain_text("Item: Alpha");
85 value_of(window.testDivTemplate.childNodes[0].childNodes[1]).should_contain_text("Item: Beta");
86 value_of(window.testDivTemplate.childNodes[0].childNodes[2]).should_contain_text("Item: Gamma");
87 value_of(window.testDivTemplate.childNodes[0].childNodes[3]).should_contain_text("Item: Pushed");
90 value_of(window.testDivTemplate.childNodes[0].childNodes[0]).should_contain_text("Item: Alpha");
91 value_of(window.testDivTemplate.childNodes[0].childNodes[1]).should_contain_text("Item: Gamma");
92 value_of(window.testDivTemplate.childNodes[0].childNodes[2]).should_contain_text("Item: Pushed");
95 'Anonymous templates may be nested': function() {
96 window.testDivTemplate.innerHTML = "<div data-bind='template: { foreach: items }'>"
97 + "<div data-bind='template: { foreach: children }'>"
98 + "(Val: <span data-bind='text: $data'></span>, Invocations: <span data-bind='text: $root.invocationCount()'></span>, Parents: <span data-bind='text: $parents.length'></span>)"
102 invocations: 0, // Verifying # invocations to be sure we're not rendering anything multiple times and discarding the results
103 items: ko.observableArray([
104 { children: ko.observableArray(['A1', 'A2', 'A3']) },
105 { children: ko.observableArray(['B1', 'B2']) }
108 viewModel.invocationCount = function() { return ++this.invocations }.bind(viewModel);
109 ko.applyBindings(viewModel, window.testDivTemplate);
111 value_of(window.testDivTemplate.childNodes[0].childNodes[0]).should_contain_text("(Val: A1, Invocations: 1, Parents: 2)(Val: A2, Invocations: 2, Parents: 2)(Val: A3, Invocations: 3, Parents: 2)");
112 value_of(window.testDivTemplate.childNodes[0].childNodes[1]).should_contain_text("(Val: B1, Invocations: 4, Parents: 2)(Val: B2, Invocations: 5, Parents: 2)");
114 // Check we can insert without causing anything else to rerender
115 viewModel.items()[1].children.unshift('ANew');
116 value_of(window.testDivTemplate.childNodes[0].childNodes[0]).should_contain_text("(Val: A1, Invocations: 1, Parents: 2)(Val: A2, Invocations: 2, Parents: 2)(Val: A3, Invocations: 3, Parents: 2)");
117 value_of(window.testDivTemplate.childNodes[0].childNodes[1]).should_contain_text("(Val: ANew, Invocations: 6, Parents: 2)(Val: B1, Invocations: 4, Parents: 2)(Val: B2, Invocations: 5, Parents: 2)");
120 'Data-bind syntax should expose parent binding context as $parent if binding with an explicit \"data\" value': function() {
121 window.testDivTemplate.innerHTML = "<div data-bind='template: { data: someItem }'>"
122 + "ValueBound: <span data-bind='text: $parent.parentProp'></span>"
124 ko.applyBindings({ someItem: {}, parentProp: 'Hello' }, window.testDivTemplate);
125 value_of(window.testDivTemplate.childNodes[0]).should_contain_text("ValueBound: Hello");
128 'Data-bind syntax should expose all ancestor binding contexts as $parents, with top frame also given as $root': function() {
129 window.testDivTemplate.innerHTML = "<div data-bind='template: { data: outerItem }'>"
130 + "<div data-bind='template: { data: middleItem }'>"
131 + "<div data-bind='template: { data: innerItem }'>("
132 + "data: <span data-bind='text: $data.val'></span>, "
133 + "parent: <span data-bind='text: $parent.val'></span>, "
134 + "parents[0]: <span data-bind='text: $parents[0].val'></span>, "
135 + "parents[1]: <span data-bind='text: $parents[1].val'></span>, "
136 + "parents.length: <span data-bind='text: $parents.length'></span>, "
137 + "root: <span data-bind='text: $root.val'></span>"
148 innerItem: { val: "INNER" }
151 }, window.testDivTemplate);
152 value_of(window.testDivTemplate.childNodes[0].childNodes[0].childNodes[0]).should_contain_text("(data: INNER, parent: MIDDLE, parents[0]: MIDDLE, parents[1]: OUTER, parents.length: 3, root: ROOT)");