1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/shared/test/browser_templater_basic.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,288 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +// Tests that the DOM Template engine works properly 1.8 + 1.9 +/* 1.10 + * These tests run both in Mozilla/Mochitest and plain browsers (as does 1.11 + * domtemplate) 1.12 + * We should endevour to keep the source in sync. 1.13 + */ 1.14 + 1.15 +var promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {}).Promise; 1.16 +var template = Cu.import("resource://gre/modules/devtools/Templater.jsm", {}).template; 1.17 + 1.18 +const TEST_URI = "http://example.com/browser/browser/devtools/shared/test/browser_templater_basic.html"; 1.19 + 1.20 +function test() { 1.21 + addTab(TEST_URI, function() { 1.22 + info("Starting DOM Templater Tests"); 1.23 + runTest(0); 1.24 + }); 1.25 +} 1.26 + 1.27 +function runTest(index) { 1.28 + var options = tests[index] = tests[index](); 1.29 + var holder = content.document.createElement('div'); 1.30 + holder.id = options.name; 1.31 + var body = content.document.body; 1.32 + body.appendChild(holder); 1.33 + holder.innerHTML = options.template; 1.34 + 1.35 + info('Running ' + options.name); 1.36 + template(holder, options.data, options.options); 1.37 + 1.38 + if (typeof options.result == 'string') { 1.39 + is(holder.innerHTML, options.result, options.name); 1.40 + } 1.41 + else { 1.42 + ok(holder.innerHTML.match(options.result) != null, 1.43 + options.name + ' result=\'' + holder.innerHTML + '\''); 1.44 + } 1.45 + 1.46 + if (options.also) { 1.47 + options.also(options); 1.48 + } 1.49 + 1.50 + function runNextTest() { 1.51 + index++; 1.52 + if (index < tests.length) { 1.53 + runTest(index); 1.54 + } 1.55 + else { 1.56 + finished(); 1.57 + } 1.58 + } 1.59 + 1.60 + if (options.later) { 1.61 + var ais = is.bind(this); 1.62 + 1.63 + function createTester(holder, options) { 1.64 + return function() { 1.65 + ais(holder.innerHTML, options.later, options.name + ' later'); 1.66 + runNextTest(); 1.67 + }.bind(this); 1.68 + } 1.69 + 1.70 + executeSoon(createTester(holder, options)); 1.71 + } 1.72 + else { 1.73 + runNextTest(); 1.74 + } 1.75 +} 1.76 + 1.77 +function finished() { 1.78 + gBrowser.removeCurrentTab(); 1.79 + info("Finishing DOM Templater Tests"); 1.80 + tests = null; 1.81 + finish(); 1.82 +} 1.83 + 1.84 +/** 1.85 + * Why have an array of functions that return data rather than just an array 1.86 + * of the data itself? Some of these tests contain calls to delayReply() which 1.87 + * sets up async processing using executeSoon(). Since the execution of these 1.88 + * tests is asynchronous, the delayed reply will probably arrive before the 1.89 + * test is executed, making the test be synchronous. So we wrap the data in a 1.90 + * function so we only set it up just before we use it. 1.91 + */ 1.92 +var tests = [ 1.93 + function() { return { 1.94 + name: 'simpleNesting', 1.95 + template: '<div id="ex1">${nested.value}</div>', 1.96 + data: { nested:{ value:'pass 1' } }, 1.97 + result: '<div id="ex1">pass 1</div>' 1.98 + };}, 1.99 + 1.100 + function() { return { 1.101 + name: 'returnDom', 1.102 + template: '<div id="ex2">${__element.ownerDocument.createTextNode(\'pass 2\')}</div>', 1.103 + options: { allowEval: true }, 1.104 + data: {}, 1.105 + result: '<div id="ex2">pass 2</div>' 1.106 + };}, 1.107 + 1.108 + function() { return { 1.109 + name: 'srcChange', 1.110 + template: '<img _src="${fred}" id="ex3">', 1.111 + data: { fred:'green.png' }, 1.112 + result: /<img( id="ex3")? src="green.png"( id="ex3")?>/ 1.113 + };}, 1.114 + 1.115 + function() { return { 1.116 + name: 'ifTrue', 1.117 + template: '<p if="${name !== \'jim\'}">hello ${name}</p>', 1.118 + options: { allowEval: true }, 1.119 + data: { name: 'fred' }, 1.120 + result: '<p>hello fred</p>' 1.121 + };}, 1.122 + 1.123 + function() { return { 1.124 + name: 'ifFalse', 1.125 + template: '<p if="${name !== \'jim\'}">hello ${name}</p>', 1.126 + options: { allowEval: true }, 1.127 + data: { name: 'jim' }, 1.128 + result: '' 1.129 + };}, 1.130 + 1.131 + function() { return { 1.132 + name: 'simpleLoop', 1.133 + template: '<p foreach="index in ${[ 1, 2, 3 ]}">${index}</p>', 1.134 + options: { allowEval: true }, 1.135 + data: {}, 1.136 + result: '<p>1</p><p>2</p><p>3</p>' 1.137 + };}, 1.138 + 1.139 + function() { return { 1.140 + name: 'loopElement', 1.141 + template: '<loop foreach="i in ${array}">${i}</loop>', 1.142 + data: { array: [ 1, 2, 3 ] }, 1.143 + result: '123' 1.144 + };}, 1.145 + 1.146 + // Bug 692028: DOMTemplate memory leak with asynchronous arrays 1.147 + // Bug 692031: DOMTemplate async loops do not drop the loop element 1.148 + function() { return { 1.149 + name: 'asyncLoopElement', 1.150 + template: '<loop foreach="i in ${array}">${i}</loop>', 1.151 + data: { array: delayReply([1, 2, 3]) }, 1.152 + result: '<span></span>', 1.153 + later: '123' 1.154 + };}, 1.155 + 1.156 + function() { return { 1.157 + name: 'saveElement', 1.158 + template: '<p save="${element}">${name}</p>', 1.159 + data: { name: 'pass 8' }, 1.160 + result: '<p>pass 8</p>', 1.161 + also: function(options) { 1.162 + ok(options.data.element.innerHTML, 'pass 9', 'saveElement saved'); 1.163 + delete options.data.element; 1.164 + } 1.165 + };}, 1.166 + 1.167 + function() { return { 1.168 + name: 'useElement', 1.169 + template: '<p id="pass9">${adjust(__element)}</p>', 1.170 + options: { allowEval: true }, 1.171 + data: { 1.172 + adjust: function(element) { 1.173 + is('pass9', element.id, 'useElement adjust'); 1.174 + return 'pass 9b' 1.175 + } 1.176 + }, 1.177 + result: '<p id="pass9">pass 9b</p>' 1.178 + };}, 1.179 + 1.180 + function() { return { 1.181 + name: 'asyncInline', 1.182 + template: '${delayed}', 1.183 + data: { delayed: delayReply('inline') }, 1.184 + result: '<span></span>', 1.185 + later: 'inline' 1.186 + };}, 1.187 + 1.188 + // Bug 692028: DOMTemplate memory leak with asynchronous arrays 1.189 + function() { return { 1.190 + name: 'asyncArray', 1.191 + template: '<p foreach="i in ${delayed}">${i}</p>', 1.192 + data: { delayed: delayReply([1, 2, 3]) }, 1.193 + result: '<span></span>', 1.194 + later: '<p>1</p><p>2</p><p>3</p>' 1.195 + };}, 1.196 + 1.197 + function() { return { 1.198 + name: 'asyncMember', 1.199 + template: '<p foreach="i in ${delayed}">${i}</p>', 1.200 + data: { delayed: [delayReply(4), delayReply(5), delayReply(6)] }, 1.201 + result: '<span></span><span></span><span></span>', 1.202 + later: '<p>4</p><p>5</p><p>6</p>' 1.203 + };}, 1.204 + 1.205 + // Bug 692028: DOMTemplate memory leak with asynchronous arrays 1.206 + function() { return { 1.207 + name: 'asyncBoth', 1.208 + template: '<p foreach="i in ${delayed}">${i}</p>', 1.209 + data: { 1.210 + delayed: delayReply([ 1.211 + delayReply(4), 1.212 + delayReply(5), 1.213 + delayReply(6) 1.214 + ]) 1.215 + }, 1.216 + result: '<span></span>', 1.217 + later: '<p>4</p><p>5</p><p>6</p>' 1.218 + };}, 1.219 + 1.220 + // Bug 701762: DOMTemplate fails when ${foo()} returns undefined 1.221 + function() { return { 1.222 + name: 'functionReturningUndefiend', 1.223 + template: '<p>${foo()}</p>', 1.224 + options: { allowEval: true }, 1.225 + data: { 1.226 + foo: function() {} 1.227 + }, 1.228 + result: '<p>undefined</p>' 1.229 + };}, 1.230 + 1.231 + // Bug 702642: DOMTemplate is relatively slow when evaluating JS ${} 1.232 + function() { return { 1.233 + name: 'propertySimple', 1.234 + template: '<p>${a.b.c}</p>', 1.235 + data: { a: { b: { c: 'hello' } } }, 1.236 + result: '<p>hello</p>' 1.237 + };}, 1.238 + 1.239 + function() { return { 1.240 + name: 'propertyPass', 1.241 + template: '<p>${Math.max(1, 2)}</p>', 1.242 + options: { allowEval: true }, 1.243 + result: '<p>2</p>' 1.244 + };}, 1.245 + 1.246 + function() { return { 1.247 + name: 'propertyFail', 1.248 + template: '<p>${Math.max(1, 2)}</p>', 1.249 + result: '<p>${Math.max(1, 2)}</p>' 1.250 + };}, 1.251 + 1.252 + // Bug 723431: DOMTemplate should allow customisation of display of 1.253 + // null/undefined values 1.254 + function() { return { 1.255 + name: 'propertyUndefAttrFull', 1.256 + template: '<p>${nullvar}|${undefinedvar1}|${undefinedvar2}</p>', 1.257 + data: { nullvar: null, undefinedvar1: undefined }, 1.258 + result: '<p>null|undefined|undefined</p>' 1.259 + };}, 1.260 + 1.261 + function() { return { 1.262 + name: 'propertyUndefAttrBlank', 1.263 + template: '<p>${nullvar}|${undefinedvar1}|${undefinedvar2}</p>', 1.264 + data: { nullvar: null, undefinedvar1: undefined }, 1.265 + options: { blankNullUndefined: true }, 1.266 + result: '<p>||</p>' 1.267 + };}, 1.268 + 1.269 + function() { return { 1.270 + name: 'propertyUndefAttrFull', 1.271 + template: '<div><p value="${nullvar}"></p><p value="${undefinedvar1}"></p><p value="${undefinedvar2}"></p></div>', 1.272 + data: { nullvar: null, undefinedvar1: undefined }, 1.273 + result: '<div><p value="null"></p><p value="undefined"></p><p value="undefined"></p></div>' 1.274 + };}, 1.275 + 1.276 + function() { return { 1.277 + name: 'propertyUndefAttrBlank', 1.278 + template: '<div><p value="${nullvar}"></p><p value="${undefinedvar1}"></p><p value="${undefinedvar2}"></p></div>', 1.279 + data: { nullvar: null, undefinedvar1: undefined }, 1.280 + options: { blankNullUndefined: true }, 1.281 + result: '<div><p value=""></p><p value=""></p><p value=""></p></div>' 1.282 + };} 1.283 +]; 1.284 + 1.285 +function delayReply(data) { 1.286 + var d = promise.defer(); 1.287 + executeSoon(function() { 1.288 + d.resolve(data); 1.289 + }); 1.290 + return d.promise; 1.291 +}