|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 // Tests that the DOM Template engine works properly |
|
5 |
|
6 /* |
|
7 * These tests run both in Mozilla/Mochitest and plain browsers (as does |
|
8 * domtemplate) |
|
9 * We should endevour to keep the source in sync. |
|
10 */ |
|
11 |
|
12 var promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {}).Promise; |
|
13 var template = Cu.import("resource://gre/modules/devtools/Templater.jsm", {}).template; |
|
14 |
|
15 const TEST_URI = "http://example.com/browser/browser/devtools/shared/test/browser_templater_basic.html"; |
|
16 |
|
17 function test() { |
|
18 addTab(TEST_URI, function() { |
|
19 info("Starting DOM Templater Tests"); |
|
20 runTest(0); |
|
21 }); |
|
22 } |
|
23 |
|
24 function runTest(index) { |
|
25 var options = tests[index] = tests[index](); |
|
26 var holder = content.document.createElement('div'); |
|
27 holder.id = options.name; |
|
28 var body = content.document.body; |
|
29 body.appendChild(holder); |
|
30 holder.innerHTML = options.template; |
|
31 |
|
32 info('Running ' + options.name); |
|
33 template(holder, options.data, options.options); |
|
34 |
|
35 if (typeof options.result == 'string') { |
|
36 is(holder.innerHTML, options.result, options.name); |
|
37 } |
|
38 else { |
|
39 ok(holder.innerHTML.match(options.result) != null, |
|
40 options.name + ' result=\'' + holder.innerHTML + '\''); |
|
41 } |
|
42 |
|
43 if (options.also) { |
|
44 options.also(options); |
|
45 } |
|
46 |
|
47 function runNextTest() { |
|
48 index++; |
|
49 if (index < tests.length) { |
|
50 runTest(index); |
|
51 } |
|
52 else { |
|
53 finished(); |
|
54 } |
|
55 } |
|
56 |
|
57 if (options.later) { |
|
58 var ais = is.bind(this); |
|
59 |
|
60 function createTester(holder, options) { |
|
61 return function() { |
|
62 ais(holder.innerHTML, options.later, options.name + ' later'); |
|
63 runNextTest(); |
|
64 }.bind(this); |
|
65 } |
|
66 |
|
67 executeSoon(createTester(holder, options)); |
|
68 } |
|
69 else { |
|
70 runNextTest(); |
|
71 } |
|
72 } |
|
73 |
|
74 function finished() { |
|
75 gBrowser.removeCurrentTab(); |
|
76 info("Finishing DOM Templater Tests"); |
|
77 tests = null; |
|
78 finish(); |
|
79 } |
|
80 |
|
81 /** |
|
82 * Why have an array of functions that return data rather than just an array |
|
83 * of the data itself? Some of these tests contain calls to delayReply() which |
|
84 * sets up async processing using executeSoon(). Since the execution of these |
|
85 * tests is asynchronous, the delayed reply will probably arrive before the |
|
86 * test is executed, making the test be synchronous. So we wrap the data in a |
|
87 * function so we only set it up just before we use it. |
|
88 */ |
|
89 var tests = [ |
|
90 function() { return { |
|
91 name: 'simpleNesting', |
|
92 template: '<div id="ex1">${nested.value}</div>', |
|
93 data: { nested:{ value:'pass 1' } }, |
|
94 result: '<div id="ex1">pass 1</div>' |
|
95 };}, |
|
96 |
|
97 function() { return { |
|
98 name: 'returnDom', |
|
99 template: '<div id="ex2">${__element.ownerDocument.createTextNode(\'pass 2\')}</div>', |
|
100 options: { allowEval: true }, |
|
101 data: {}, |
|
102 result: '<div id="ex2">pass 2</div>' |
|
103 };}, |
|
104 |
|
105 function() { return { |
|
106 name: 'srcChange', |
|
107 template: '<img _src="${fred}" id="ex3">', |
|
108 data: { fred:'green.png' }, |
|
109 result: /<img( id="ex3")? src="green.png"( id="ex3")?>/ |
|
110 };}, |
|
111 |
|
112 function() { return { |
|
113 name: 'ifTrue', |
|
114 template: '<p if="${name !== \'jim\'}">hello ${name}</p>', |
|
115 options: { allowEval: true }, |
|
116 data: { name: 'fred' }, |
|
117 result: '<p>hello fred</p>' |
|
118 };}, |
|
119 |
|
120 function() { return { |
|
121 name: 'ifFalse', |
|
122 template: '<p if="${name !== \'jim\'}">hello ${name}</p>', |
|
123 options: { allowEval: true }, |
|
124 data: { name: 'jim' }, |
|
125 result: '' |
|
126 };}, |
|
127 |
|
128 function() { return { |
|
129 name: 'simpleLoop', |
|
130 template: '<p foreach="index in ${[ 1, 2, 3 ]}">${index}</p>', |
|
131 options: { allowEval: true }, |
|
132 data: {}, |
|
133 result: '<p>1</p><p>2</p><p>3</p>' |
|
134 };}, |
|
135 |
|
136 function() { return { |
|
137 name: 'loopElement', |
|
138 template: '<loop foreach="i in ${array}">${i}</loop>', |
|
139 data: { array: [ 1, 2, 3 ] }, |
|
140 result: '123' |
|
141 };}, |
|
142 |
|
143 // Bug 692028: DOMTemplate memory leak with asynchronous arrays |
|
144 // Bug 692031: DOMTemplate async loops do not drop the loop element |
|
145 function() { return { |
|
146 name: 'asyncLoopElement', |
|
147 template: '<loop foreach="i in ${array}">${i}</loop>', |
|
148 data: { array: delayReply([1, 2, 3]) }, |
|
149 result: '<span></span>', |
|
150 later: '123' |
|
151 };}, |
|
152 |
|
153 function() { return { |
|
154 name: 'saveElement', |
|
155 template: '<p save="${element}">${name}</p>', |
|
156 data: { name: 'pass 8' }, |
|
157 result: '<p>pass 8</p>', |
|
158 also: function(options) { |
|
159 ok(options.data.element.innerHTML, 'pass 9', 'saveElement saved'); |
|
160 delete options.data.element; |
|
161 } |
|
162 };}, |
|
163 |
|
164 function() { return { |
|
165 name: 'useElement', |
|
166 template: '<p id="pass9">${adjust(__element)}</p>', |
|
167 options: { allowEval: true }, |
|
168 data: { |
|
169 adjust: function(element) { |
|
170 is('pass9', element.id, 'useElement adjust'); |
|
171 return 'pass 9b' |
|
172 } |
|
173 }, |
|
174 result: '<p id="pass9">pass 9b</p>' |
|
175 };}, |
|
176 |
|
177 function() { return { |
|
178 name: 'asyncInline', |
|
179 template: '${delayed}', |
|
180 data: { delayed: delayReply('inline') }, |
|
181 result: '<span></span>', |
|
182 later: 'inline' |
|
183 };}, |
|
184 |
|
185 // Bug 692028: DOMTemplate memory leak with asynchronous arrays |
|
186 function() { return { |
|
187 name: 'asyncArray', |
|
188 template: '<p foreach="i in ${delayed}">${i}</p>', |
|
189 data: { delayed: delayReply([1, 2, 3]) }, |
|
190 result: '<span></span>', |
|
191 later: '<p>1</p><p>2</p><p>3</p>' |
|
192 };}, |
|
193 |
|
194 function() { return { |
|
195 name: 'asyncMember', |
|
196 template: '<p foreach="i in ${delayed}">${i}</p>', |
|
197 data: { delayed: [delayReply(4), delayReply(5), delayReply(6)] }, |
|
198 result: '<span></span><span></span><span></span>', |
|
199 later: '<p>4</p><p>5</p><p>6</p>' |
|
200 };}, |
|
201 |
|
202 // Bug 692028: DOMTemplate memory leak with asynchronous arrays |
|
203 function() { return { |
|
204 name: 'asyncBoth', |
|
205 template: '<p foreach="i in ${delayed}">${i}</p>', |
|
206 data: { |
|
207 delayed: delayReply([ |
|
208 delayReply(4), |
|
209 delayReply(5), |
|
210 delayReply(6) |
|
211 ]) |
|
212 }, |
|
213 result: '<span></span>', |
|
214 later: '<p>4</p><p>5</p><p>6</p>' |
|
215 };}, |
|
216 |
|
217 // Bug 701762: DOMTemplate fails when ${foo()} returns undefined |
|
218 function() { return { |
|
219 name: 'functionReturningUndefiend', |
|
220 template: '<p>${foo()}</p>', |
|
221 options: { allowEval: true }, |
|
222 data: { |
|
223 foo: function() {} |
|
224 }, |
|
225 result: '<p>undefined</p>' |
|
226 };}, |
|
227 |
|
228 // Bug 702642: DOMTemplate is relatively slow when evaluating JS ${} |
|
229 function() { return { |
|
230 name: 'propertySimple', |
|
231 template: '<p>${a.b.c}</p>', |
|
232 data: { a: { b: { c: 'hello' } } }, |
|
233 result: '<p>hello</p>' |
|
234 };}, |
|
235 |
|
236 function() { return { |
|
237 name: 'propertyPass', |
|
238 template: '<p>${Math.max(1, 2)}</p>', |
|
239 options: { allowEval: true }, |
|
240 result: '<p>2</p>' |
|
241 };}, |
|
242 |
|
243 function() { return { |
|
244 name: 'propertyFail', |
|
245 template: '<p>${Math.max(1, 2)}</p>', |
|
246 result: '<p>${Math.max(1, 2)}</p>' |
|
247 };}, |
|
248 |
|
249 // Bug 723431: DOMTemplate should allow customisation of display of |
|
250 // null/undefined values |
|
251 function() { return { |
|
252 name: 'propertyUndefAttrFull', |
|
253 template: '<p>${nullvar}|${undefinedvar1}|${undefinedvar2}</p>', |
|
254 data: { nullvar: null, undefinedvar1: undefined }, |
|
255 result: '<p>null|undefined|undefined</p>' |
|
256 };}, |
|
257 |
|
258 function() { return { |
|
259 name: 'propertyUndefAttrBlank', |
|
260 template: '<p>${nullvar}|${undefinedvar1}|${undefinedvar2}</p>', |
|
261 data: { nullvar: null, undefinedvar1: undefined }, |
|
262 options: { blankNullUndefined: true }, |
|
263 result: '<p>||</p>' |
|
264 };}, |
|
265 |
|
266 function() { return { |
|
267 name: 'propertyUndefAttrFull', |
|
268 template: '<div><p value="${nullvar}"></p><p value="${undefinedvar1}"></p><p value="${undefinedvar2}"></p></div>', |
|
269 data: { nullvar: null, undefinedvar1: undefined }, |
|
270 result: '<div><p value="null"></p><p value="undefined"></p><p value="undefined"></p></div>' |
|
271 };}, |
|
272 |
|
273 function() { return { |
|
274 name: 'propertyUndefAttrBlank', |
|
275 template: '<div><p value="${nullvar}"></p><p value="${undefinedvar1}"></p><p value="${undefinedvar2}"></p></div>', |
|
276 data: { nullvar: null, undefinedvar1: undefined }, |
|
277 options: { blankNullUndefined: true }, |
|
278 result: '<div><p value=""></p><p value=""></p><p value=""></p></div>' |
|
279 };} |
|
280 ]; |
|
281 |
|
282 function delayReply(data) { |
|
283 var d = promise.defer(); |
|
284 executeSoon(function() { |
|
285 d.resolve(data); |
|
286 }); |
|
287 return d.promise; |
|
288 } |