michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: // Tests that the DOM Template engine works properly michael@0: michael@0: /* michael@0: * These tests run both in Mozilla/Mochitest and plain browsers (as does michael@0: * domtemplate) michael@0: * We should endevour to keep the source in sync. michael@0: */ michael@0: michael@0: var promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {}).Promise; michael@0: var template = Cu.import("resource://gre/modules/devtools/Templater.jsm", {}).template; michael@0: michael@0: const TEST_URI = "http://example.com/browser/browser/devtools/shared/test/browser_templater_basic.html"; michael@0: michael@0: function test() { michael@0: addTab(TEST_URI, function() { michael@0: info("Starting DOM Templater Tests"); michael@0: runTest(0); michael@0: }); michael@0: } michael@0: michael@0: function runTest(index) { michael@0: var options = tests[index] = tests[index](); michael@0: var holder = content.document.createElement('div'); michael@0: holder.id = options.name; michael@0: var body = content.document.body; michael@0: body.appendChild(holder); michael@0: holder.innerHTML = options.template; michael@0: michael@0: info('Running ' + options.name); michael@0: template(holder, options.data, options.options); michael@0: michael@0: if (typeof options.result == 'string') { michael@0: is(holder.innerHTML, options.result, options.name); michael@0: } michael@0: else { michael@0: ok(holder.innerHTML.match(options.result) != null, michael@0: options.name + ' result=\'' + holder.innerHTML + '\''); michael@0: } michael@0: michael@0: if (options.also) { michael@0: options.also(options); michael@0: } michael@0: michael@0: function runNextTest() { michael@0: index++; michael@0: if (index < tests.length) { michael@0: runTest(index); michael@0: } michael@0: else { michael@0: finished(); michael@0: } michael@0: } michael@0: michael@0: if (options.later) { michael@0: var ais = is.bind(this); michael@0: michael@0: function createTester(holder, options) { michael@0: return function() { michael@0: ais(holder.innerHTML, options.later, options.name + ' later'); michael@0: runNextTest(); michael@0: }.bind(this); michael@0: } michael@0: michael@0: executeSoon(createTester(holder, options)); michael@0: } michael@0: else { michael@0: runNextTest(); michael@0: } michael@0: } michael@0: michael@0: function finished() { michael@0: gBrowser.removeCurrentTab(); michael@0: info("Finishing DOM Templater Tests"); michael@0: tests = null; michael@0: finish(); michael@0: } michael@0: michael@0: /** michael@0: * Why have an array of functions that return data rather than just an array michael@0: * of the data itself? Some of these tests contain calls to delayReply() which michael@0: * sets up async processing using executeSoon(). Since the execution of these michael@0: * tests is asynchronous, the delayed reply will probably arrive before the michael@0: * test is executed, making the test be synchronous. So we wrap the data in a michael@0: * function so we only set it up just before we use it. michael@0: */ michael@0: var tests = [ michael@0: function() { return { michael@0: name: 'simpleNesting', michael@0: template: '
hello ${name}
', michael@0: options: { allowEval: true }, michael@0: data: { name: 'fred' }, michael@0: result: 'hello fred
' michael@0: };}, michael@0: michael@0: function() { return { michael@0: name: 'ifFalse', michael@0: template: 'hello ${name}
', michael@0: options: { allowEval: true }, michael@0: data: { name: 'jim' }, michael@0: result: '' michael@0: };}, michael@0: michael@0: function() { return { michael@0: name: 'simpleLoop', michael@0: template: '${index}
', michael@0: options: { allowEval: true }, michael@0: data: {}, michael@0: result: '1
2
3
' michael@0: };}, michael@0: michael@0: function() { return { michael@0: name: 'loopElement', michael@0: template: '${name}
', michael@0: data: { name: 'pass 8' }, michael@0: result: 'pass 8
', michael@0: also: function(options) { michael@0: ok(options.data.element.innerHTML, 'pass 9', 'saveElement saved'); michael@0: delete options.data.element; michael@0: } michael@0: };}, michael@0: michael@0: function() { return { michael@0: name: 'useElement', michael@0: template: '${adjust(__element)}
', michael@0: options: { allowEval: true }, michael@0: data: { michael@0: adjust: function(element) { michael@0: is('pass9', element.id, 'useElement adjust'); michael@0: return 'pass 9b' michael@0: } michael@0: }, michael@0: result: 'pass 9b
' michael@0: };}, michael@0: michael@0: function() { return { michael@0: name: 'asyncInline', michael@0: template: '${delayed}', michael@0: data: { delayed: delayReply('inline') }, michael@0: result: '', michael@0: later: 'inline' michael@0: };}, michael@0: michael@0: // Bug 692028: DOMTemplate memory leak with asynchronous arrays michael@0: function() { return { michael@0: name: 'asyncArray', michael@0: template: '${i}
', michael@0: data: { delayed: delayReply([1, 2, 3]) }, michael@0: result: '', michael@0: later: '1
2
3
' michael@0: };}, michael@0: michael@0: function() { return { michael@0: name: 'asyncMember', michael@0: template: '${i}
', michael@0: data: { delayed: [delayReply(4), delayReply(5), delayReply(6)] }, michael@0: result: '', michael@0: later: '4
5
6
' michael@0: };}, michael@0: michael@0: // Bug 692028: DOMTemplate memory leak with asynchronous arrays michael@0: function() { return { michael@0: name: 'asyncBoth', michael@0: template: '${i}
', michael@0: data: { michael@0: delayed: delayReply([ michael@0: delayReply(4), michael@0: delayReply(5), michael@0: delayReply(6) michael@0: ]) michael@0: }, michael@0: result: '', michael@0: later: '4
5
6
' michael@0: };}, michael@0: michael@0: // Bug 701762: DOMTemplate fails when ${foo()} returns undefined michael@0: function() { return { michael@0: name: 'functionReturningUndefiend', michael@0: template: '${foo()}
', michael@0: options: { allowEval: true }, michael@0: data: { michael@0: foo: function() {} michael@0: }, michael@0: result: 'undefined
' michael@0: };}, michael@0: michael@0: // Bug 702642: DOMTemplate is relatively slow when evaluating JS ${} michael@0: function() { return { michael@0: name: 'propertySimple', michael@0: template: '${a.b.c}
', michael@0: data: { a: { b: { c: 'hello' } } }, michael@0: result: 'hello
' michael@0: };}, michael@0: michael@0: function() { return { michael@0: name: 'propertyPass', michael@0: template: '${Math.max(1, 2)}
', michael@0: options: { allowEval: true }, michael@0: result: '2
' michael@0: };}, michael@0: michael@0: function() { return { michael@0: name: 'propertyFail', michael@0: template: '${Math.max(1, 2)}
', michael@0: result: '${Math.max(1, 2)}
' michael@0: };}, michael@0: michael@0: // Bug 723431: DOMTemplate should allow customisation of display of michael@0: // null/undefined values michael@0: function() { return { michael@0: name: 'propertyUndefAttrFull', michael@0: template: '${nullvar}|${undefinedvar1}|${undefinedvar2}
', michael@0: data: { nullvar: null, undefinedvar1: undefined }, michael@0: result: 'null|undefined|undefined
' michael@0: };}, michael@0: michael@0: function() { return { michael@0: name: 'propertyUndefAttrBlank', michael@0: template: '${nullvar}|${undefinedvar1}|${undefinedvar2}
', michael@0: data: { nullvar: null, undefinedvar1: undefined }, michael@0: options: { blankNullUndefined: true }, michael@0: result: '||
' michael@0: };}, michael@0: michael@0: function() { return { michael@0: name: 'propertyUndefAttrFull', michael@0: template: '