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