1// Copyright 2006 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12// implied. See the License for the specific language governing
13// permissions and limitations under the License.
14/**
15 * @author Steffen Meschkat (mesch@google.com)
16 * @fileoverview Unittest and examples for jstemplates.
17 */
18
19function jstWrap(data, template) {
20  return jstProcess(new JsEvalContext(data), template);
21}
22
23function testJstSelect() {
24  // Template cardinality from jsselect.
25  var t = document.getElementById('t1');
26  var d = {
27    items: [ 'A', 'B', 'C', '' ]
28  }
29  jstWrap(d, t);
30
31  var h = t.innerHTML;
32  var clone = domCloneNode(t);
33  assertTrue(/>A<\/div>/.test(h));
34  assertTrue(/>B<\/div>/.test(h));
35  assertTrue(/>C<\/div>/.test(h));
36  assertTrue(/><\/div>/.test(h));
37
38  // Reprocessing with identical data.
39  jstWrap(d, t);
40  assertAttributesMatch(t, clone);
41
42  // Reprocessing with changed data.
43  d.items[1] = 'BB';
44  jstWrap(d, t);
45
46  h = t.innerHTML;
47  assertTrue(/>A<\/div>/.test(h));
48  assertFalse(/>B<\/div>/.test(h));
49  assertTrue(/>BB<\/div>/.test(h));
50  assertTrue(/>C<\/div>/.test(h));
51
52  // Reprocessing with dropped data.
53  d.items.pop();
54  d.items.pop();
55  jstWrap(d, t);
56  h = t.innerHTML;
57  assertTrue(/>A<\/div>/.test(h));
58  assertTrue(/>BB<\/div>/.test(h));
59  assertFalse(/>C<\/div>/.test(h));
60  assertFalse(/><\/div>/.test(h));
61
62  // Reprocessing with dropped data, once more.
63  d.items.pop();
64  jstWrap(d, t);
65  h = t.innerHTML;
66  assertTrue(/>A<\/div>/.test(h));
67  assertFalse(/>BB<\/div>/.test(h));
68  assertFalse(/>C<\/div>/.test(h));
69
70  // Reprocessing with empty data -- the last template instance is
71  // preserved, and only hidden.
72  d.items.pop();
73  jstWrap(d, t);
74
75  assertTrue(/>A<\/div>/.test(h));
76  assertFalse(/>BB<\/div>/.test(h));
77  assertFalse(/>C<\/div>/.test(h));
78
79  // Reprocessing with added data.
80  d.items.push('D');
81  jstWrap(d, t);
82  h = t.innerHTML;
83  assertFalse(/>A<\/div>/.test(h));
84  assertTrue(/>D<\/div>/.test(h));
85}
86
87function testJstDisplay() {
88  var t = document.getElementById('t2');
89  var d = {
90    display: true
91  }
92  jstWrap(d, t);
93
94  var h = t.innerHTML;
95  assertFalse(/display:\s*none/.test(h));
96
97  d.display = false;
98  jstWrap(d, t);
99
100  h = t.innerHTML;
101  assertTrue(/display:\s*none/.test(h));
102
103  // Check that 'this' within js expressions is the template node
104  t = document.getElementById('t2a');
105  d = {
106    showId: 'x'
107  };
108  jstWrap(d, t);
109
110  h = t.innerHTML;
111  assertFalse(/display:\s*none/.test(h));
112
113  d.showId = 'y';
114  jstWrap(d, t);
115
116  h = t.innerHTML;
117  assertTrue(/display:\s*none/.test(h));
118}
119
120function stringContains(str, sub) {
121  return str.indexOf(sub) != -1;
122}
123
124function testJseval() {
125  var data = {};
126
127  var counter = 0;
128  var ctx = new JsEvalContext(data);
129  ctx.setVariable("callback1", function() {
130    ++counter;
131  });
132  ctx.setVariable("callback2", function() {
133    counter *= 2;
134  });
135
136  jstProcess(ctx, document.getElementById('testJseval1'));
137  assertEquals("testJseval1", 1, counter);
138
139  jstProcess(ctx, document.getElementById('testJseval2'));
140  assertEquals("testJseval2", 4, counter);
141}
142
143function testJstValues() {
144  var t = document.getElementById('t3');
145  var d = {};
146  jstWrap(d, t);
147  var h = t.innerHTML;
148  assertTrue(stringContains(h, 'http://maps.google.com/'));
149  var t3a = document.getElementById('t3a');
150  assertEquals('http://maps.google.com/', t3a.foo.bar.baz);
151  assertEquals('http://maps.google.com/', t3a.bar);
152  assertEquals('red', t3a.style.backgroundColor);
153}
154
155function testJstTransclude() {
156  var t = document.getElementById('t4');
157  var p = document.getElementById('parent');
158  var d = {};
159  jstWrap(d, t);
160  var h = p.innerHTML;
161  assertTrue(h, stringContains(h, 'http://maps.google.com/'));
162}
163
164function assertAttributesMatch(first, second) {
165  assertEquals('assertAttributesMatch: number of child nodes',
166               jsLength(first.childNodes), jsLength(second.childNodes));
167  var b = second.firstChild;
168  for (var a = first.firstChild; a; a = a.nextSibling) {
169    var att = a.attributes;
170    if (att) {
171      assertTrue(b.attributes != null);
172      assertEquals('assertAttributesMatch: number of attribute nodes',
173                   att.length, b.attributes.length);
174      for (var i = 0; i < jsLength(att); i++) {
175        var a = att[i];
176        assertEquals('assertAttributesMatch: value of attribute ' + a.name,
177                     a.value, b.getAttribute(a.name));
178      }
179    } else {
180      assertNull(b.attributes);
181    }
182    b = b.nextSibling;
183  }
184}
185
186function testJsskip() {
187  var div = domCreateElement(document, "DIV");
188  div.innerHTML = [
189      '<div jseval="outercallback()" jsskip="1">',
190      '<div jseval="innercallback()">',
191      '</div>',
192      '</div>'
193  ].join('');
194
195  var data = {};
196  var ctx = new JsEvalContext(data);
197  var outerCalled = false;
198  ctx.setVariable("outercallback", function() {
199    outerCalled = true;
200  });
201  var innerCalled = false;
202  ctx.setVariable("innercallback", function() {
203    innerCalled = true;
204  });
205  jstProcess(ctx, div);
206
207  assertTrue(outerCalled);
208  assertFalse(innerCalled);
209}
210
211function testScalarContext() {
212  var t = document.getElementById('testScalarContext');
213
214  jstWrap(true, t);
215  assertTrue(/>true</.test(t.innerHTML));
216
217  jstWrap(false, t);
218  assertTrue(/>false</.test(t.innerHTML));
219
220  jstWrap(0, t);
221  assertTrue(/>0</.test(t.innerHTML));
222
223  jstWrap("foo", t);
224  assertTrue(/>foo</.test(t.innerHTML));
225
226  jstWrap(undefined, t);
227  assertTrue(/>undefined</.test(t.innerHTML));
228
229  jstWrap(null, t);
230  assertTrue(/>null</.test(t.innerHTML));
231}
232
233function testJstLoadTemplate() {
234  var wrapperId = 'testJstLoadTemplateWrapper';
235  var id = 'testJstLoadTemplate';
236  jstLoadTemplate_(document, '<div id="' + id + '">content</div>', wrapperId);
237  var wrapperElem = document.getElementById(wrapperId);
238  assertTrue('Expected wrapper element to be in document',
239             !!wrapperElem);
240  var newTemplate = document.getElementById(id);
241  assertTrue('Expected newly loaded template to be in document',
242             !!newTemplate);
243  assertTrue('Expected wrapper to be grandparent of template',
244             newTemplate.parentNode.parentNode == wrapperElem);
245
246  // Make sure the next template loaded with the same wrapper id re-uses the
247  // wrapper element.
248  var id2 = 'testJstLoadTemplate2';
249  jstLoadTemplate_(document, '<div id="' + id2 + '">content</div>', wrapperId);
250  var newTemplate2 = document.getElementById(id2);
251  assertTrue('Expected newly loaded template to be in document',
252             !!newTemplate2);
253  assertTrue('Expected wrapper to be grandparent of template',
254             newTemplate2.parentNode.parentNode == wrapperElem);
255}
256
257function testJstGetTemplateFromDom() {
258  var element;
259  // Get by id a template in the document
260  // Success
261  element = jstGetTemplate('t1');
262  assertTrue("Asserted jstGetTemplate('t1') to return a dom element",
263             !!element);
264  // Failure
265  element = jstGetTemplate('asdf');
266  assertFalse("Asserted jstGetTemplate('asdf') to return null",
267              !!element);
268}
269
270function testJstGetTemplateFromFunction() {
271  var element;
272  // Fetch a jstemplate by id from within a html string, passed via a function.
273  function returnHtmlWithId(id) {
274    var html =
275        '<div>' +
276        '<div id="' + id + '">Here is the template</div>' +
277        '</div>';
278    return html;
279  }
280  // Success
281  element = jstGetTemplate('template',
282                           partial(returnHtmlWithId, 'template'));
283  assertTrue("Expected jstGetTemplate('template') to return a dom element",
284             !!element);
285
286  // Failure
287  element = jstGetTemplate('asdf',
288                           partial(returnHtmlWithId, 'zxcv'));
289  assertFalse("Expected jstGetTemplate('zxcv') to return null",
290              !!element);
291}
292
293function testPrepareNode() {
294  var id, node;
295  // Reset the cache so we're testing from a known state.
296  JstProcessor.jstCache_ = {};
297  JstProcessor.jstCache_[0] = {};
298
299  // Skip pre-processed nodes.  Preprocessed nodes are those with a
300  // PROP_jstcache property.
301  var t = document.getElementById('t1');
302  var caches = [];
303  caches.push(JstProcessor.prepareNode_(t));
304  caches.push(JstProcessor.prepareNode_(t));
305  assertEquals('The same cache should be returned on each call to prepareNode',
306               caches[0], caches[1]);
307
308  // Preprocessing a node with a jst attribute should return a valid struct
309  id = 'testPrepareNodeWithAttributes';
310  jstLoadTemplate_(document, '<div id="' + id + '" jsskip="1"></div>');
311  node = document.getElementById(id);
312  var cache = JstProcessor.prepareNode_(node);
313  try {
314    var jsskip = cache['jsskip']({}, {});
315  } catch (e) {
316    fail('Exception when evaluating jsskip from cache');
317  }
318  assertEquals(1, jsskip);
319}
320
321
322function testPrepareNodeWithNoAttributes() {
323  // Preprocessing a node with no jst attributes should return null
324  var id = 'testPrepareNodeNoAttributes';
325  jstLoadTemplate_(document, '<div id="' + id + '"></div>');
326  var node = document.getElementById(id);
327  assertEquals('prepareNode with no jst attributes should return default',
328               JstProcessor.jstcache_[0], JstProcessor.prepareNode_(node));
329}
330
331
332function testJsVars() {
333  var template = document.createElement('div');
334  document.body.appendChild(template);
335  template.innerHTML = '<div jsvars="foo:\'foo\';bar:true;$baz:1"></div>';
336
337  var context = new JsEvalContext;
338  jstProcess(context, template);
339
340  assertEquals('foo', context.getVariable('foo'));
341  assertEquals(1, context.getVariable('$baz'));
342  assertTrue(context.getVariable('bar'));
343  assertUndefined(context.getVariable('foobar'));
344}
345
346
347function testCacheReuse() {
348  var template = document.createElement('div');
349  document.body.appendChild(template);
350  template.innerHTML =
351    '<div jsvars="foo:\'foo\';bar:true;$baz:1"></div>' +
352    '<span jsvars="foo:\'foo\';bar:true;$baz:1"></span>';
353  JstProcessor.prepareTemplate_(template);
354  assertEquals(template.firstChild.getAttribute(ATT_jstcache),
355               template.lastChild.getAttribute(ATT_jstcache));
356}
357