diff options
Diffstat (limited to 'tools/addon-sdk-1.12/test')
158 files changed, 23402 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.12/test/addons/l10n/data/test-localization.html b/tools/addon-sdk-1.12/test/addons/l10n/data/test-localization.html new file mode 100644 index 0000000..5428863 --- /dev/null +++ b/tools/addon-sdk-1.12/test/addons/l10n/data/test-localization.html @@ -0,0 +1,24 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<html> + <head> + <meta charset="UTF-8"> + <title>HTML Localization</title> + </head> + <body> + <div data-l10n-id="Not translated">Kept as-is</div> + <ul data-l10n-id="Translated"> + <li>Inner html content is replaced,</li> + <li data-l10n-id="text-content"> + Elements with data-l10n-id attribute whose parent element is translated + will be replaced by the content of the translation. + </li> + </ul> + <div data-l10n-id="text-content">No</div> + <div data-l10n-id="Translated"> + A data-l10n-id value can be used in multiple elements + </div> + </body> +</html diff --git a/tools/addon-sdk-1.12/test/addons/l10n/locale/en-GB.properties b/tools/addon-sdk-1.12/test/addons/l10n/locale/en-GB.properties new file mode 100644 index 0000000..e625690 --- /dev/null +++ b/tools/addon-sdk-1.12/test/addons/l10n/locale/en-GB.properties @@ -0,0 +1,16 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Translated= Yes + +text-content=no <b>HTML</b> injection + +downloadsCount=%d downloads +downloadsCount[one]=one download + +pluralTest=fallback to other +pluralTest[zero]=optional zero form + +explicitPlural[one]=one +explicitPlural[other]=other diff --git a/tools/addon-sdk-1.12/test/addons/l10n/locale/eo.properties b/tools/addon-sdk-1.12/test/addons/l10n/locale/eo.properties new file mode 100644 index 0000000..a979fca --- /dev/null +++ b/tools/addon-sdk-1.12/test/addons/l10n/locale/eo.properties @@ -0,0 +1,5 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Translated= jes diff --git a/tools/addon-sdk-1.12/test/addons/l10n/locale/fr-FR.properties b/tools/addon-sdk-1.12/test/addons/l10n/locale/fr-FR.properties new file mode 100644 index 0000000..2c5ffbb --- /dev/null +++ b/tools/addon-sdk-1.12/test/addons/l10n/locale/fr-FR.properties @@ -0,0 +1,14 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Translated= Oui + +placeholderString= Placeholder %s + +# Plural forms +%d downloads=%d téléchargements +%d downloads[one]=%d téléchargement + +downloadsCount=%d téléchargements +downloadsCount[one]=%d téléchargement diff --git a/tools/addon-sdk-1.12/test/addons/l10n/main.js b/tools/addon-sdk-1.12/test/addons/l10n/main.js new file mode 100644 index 0000000..b4e980f --- /dev/null +++ b/tools/addon-sdk-1.12/test/addons/l10n/main.js @@ -0,0 +1,181 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const prefs = require("sdk/preferences/service"); +const { Loader } = require('sdk/test/loader'); +const { resolveURI } = require('toolkit/loader'); +const { rootURI } = require("@loader/options"); + +const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; +const PREF_SELECTED_LOCALE = "general.useragent.locale"; + +function setLocale(locale) { + prefs.set(PREF_MATCH_OS_LOCALE, false); + prefs.set(PREF_SELECTED_LOCALE, locale); +} + +function resetLocale() { + prefs.reset(PREF_MATCH_OS_LOCALE); + prefs.reset(PREF_SELECTED_LOCALE); +} + +function definePseudo(loader, id, exports) { + let uri = resolveURI(id, loader.mapping); + loader.modules[uri] = { exports: exports }; +} + +function createTest(locale, testFunction) { + return function (assert, done) { + let loader = Loader(module); + // Change the locale before loading new l10n modules in order to load + // the right .json file + setLocale(locale); + // Initialize main l10n module in order to load new locale files + loader.require("sdk/l10n/loader"). + load(rootURI). + then(function success(data) { + definePseudo(loader, '@l10n/data', data); + // Execute the given test function + try { + testFunction(assert, loader, function onDone() { + loader.unload(); + resetLocale(); + done(); + }); + } + catch(e) { + console.exception(e); + } + }, + function failure(error) { + assert.fail("Unable to load locales: " + error); + }); + }; +} + +exports.testExactMatching = createTest("fr-FR", function(assert, loader, done) { + let _ = loader.require("sdk/l10n").get; + assert.equal(_("Not translated"), "Not translated", + "Key not translated"); + assert.equal(_("Translated"), "Oui", + "Simple key translated"); + + // Placeholders + assert.equal(_("placeholderString", "works"), "Placeholder works", + "Value with placeholder"); + assert.equal(_("Placeholder %s", "works"), "Placeholder works", + "Key without value but with placeholder"); + assert.equal(_("Placeholders %2s %1s %s.", "working", "are", "correctly"), + "Placeholders are working correctly.", + "Multiple placeholders"); + + // Plurals + assert.equal(_("downloadsCount", 0), + "0 téléchargement", + "PluralForm form 'one' for 0 in french"); + assert.equal(_("downloadsCount", 1), + "1 téléchargement", + "PluralForm form 'one' for 1 in french"); + assert.equal(_("downloadsCount", 2), + "2 téléchargements", + "PluralForm form 'other' for n > 1 in french"); + + done(); +}); + +exports.testHtmlLocalization = createTest("en-GB", function(assert, loader, done) { + + // Ensure initing html component that watch document creations + // Note that this module is automatically initialized in + // cuddlefish.js:Loader.main in regular addons. But it isn't for unit tests. + let loaderHtmlL10n = loader.require("sdk/l10n/html"); + loaderHtmlL10n.enable(); + + let uri = require("sdk/self").data.url("test-localization.html"); + let worker = loader.require("sdk/page-worker").Page({ + contentURL: uri, + contentScript: "new " + function ContentScriptScope() { + let nodes = document.body.querySelectorAll("*[data-l10n-id]"); + self.postMessage([nodes[0].innerHTML, + nodes[1].innerHTML, + nodes[2].innerHTML, + nodes[3].innerHTML]); + }, + onMessage: function (data) { + assert.equal( + data[0], + "Kept as-is", + "Nodes with unknown id in .properties are kept 'as-is'" + ); + assert.equal(data[1], "Yes", "HTML is translated"); + assert.equal( + data[2], + "no <b>HTML</b> injection", + "Content from .properties is text content; HTML can't be injected." + ); + assert.equal(data[3], "Yes", "Multiple elements with same data-l10n-id are accepted."); + + done(); + } + }); + +}); + +exports.testEnUsLocaleName = createTest("en-US", function(assert, loader, done) { + let _ = loader.require("sdk/l10n").get; + + assert.equal(_("Not translated"), "Not translated", + "String w/o translation is kept as-is"); + assert.equal(_("Translated"), "Yes", + "String with translation is correctly translated"); + + // Check plural forms regular matching + assert.equal(_("downloadsCount", 0), + "0 downloads", + "PluralForm form 'other' for 0 in english"); + assert.equal(_("downloadsCount", 1), + "one download", + "PluralForm form 'one' for 1 in english"); + assert.equal(_("downloadsCount", 2), + "2 downloads", + "PluralForm form 'other' for n != 1 in english"); + + // Check optional plural forms + assert.equal(_("pluralTest", 0), + "optional zero form", + "PluralForm form 'zero' can be optionaly specified. (Isn't mandatory in english)"); + assert.equal(_("pluralTest", 1), + "fallback to other", + "If the specific plural form is missing, we fallback to 'other'"); + + // Ensure that we can omit specifying the generic key without [other] + // key[one] = ... + // key[other] = ... # Instead of `key = ...` + assert.equal(_("explicitPlural", 1), + "one", + "PluralForm form can be omitting generic key [i.e. without ...[other] at end of key)"); + assert.equal(_("explicitPlural", 10), + "other", + "PluralForm form can be omitting generic key [i.e. without ...[other] at end of key)"); + + done(); +}); + +exports.testShortLocaleName = createTest("eo", function(assert, loader, done) { + let _ = loader.require("sdk/l10n").get; + assert.equal(_("Not translated"), "Not translated", + "String w/o translation is kept as-is"); + assert.equal(_("Translated"), "jes", + "String with translation is correctly translated"); + + done(); +}); + + +// Before running tests, disable HTML service which is automatially enabled +// in api-utils/addon/runner.js +require('sdk/l10n/html').disable(); + +require("sdk/test/runner").runTestsFromModule(module); diff --git a/tools/addon-sdk-1.12/test/addons/l10n/package.json b/tools/addon-sdk-1.12/test/addons/l10n/package.json new file mode 100644 index 0000000..a13c952 --- /dev/null +++ b/tools/addon-sdk-1.12/test/addons/l10n/package.json @@ -0,0 +1,3 @@ +{ + "id": "test-l10n" +}
\ No newline at end of file diff --git a/tools/addon-sdk-1.12/test/commonjs-test-adapter/asserts.js b/tools/addon-sdk-1.12/test/commonjs-test-adapter/asserts.js new file mode 100644 index 0000000..8618ace --- /dev/null +++ b/tools/addon-sdk-1.12/test/commonjs-test-adapter/asserts.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const AssertBase = require("test/assert").Assert; + +/** + * Generates custom assertion constructors that may be bundled with a test + * suite. + * @params {String} + * names of assertion function to be added to the generated Assert. + */ +function Assert() { + let assertDescriptor = {}; + Array.forEach(arguments, function(name) { + assertDescriptor[name] = { value: function(message) { + this.pass(message); + }} + }); + + return function Assert() { + return Object.create(AssertBase.apply(null, arguments), assertDescriptor); + }; +} + +exports["test suite"] = { + Assert: Assert("foo"), + "test that custom assertor is passed to test function": function(assert) { + assert.ok("foo" in assert, "custom assertion function `foo` is defined"); + assert.foo("custom assertion function `foo` is called"); + }, + "test sub suite": { + "test that `Assert` is inherited by sub suits": function(assert) { + assert.ok("foo" in assert, "assertion function `foo` is not defined"); + }, + "test sub sub suite": { + Assert: Assert("bar"), + "test that custom assertor is passed to test function": function(assert) { + assert.ok("bar" in assert, + "custom assertion function `bar` is defined"); + assert.bar("custom assertion function `bar` is called"); + }, + "test that `Assert` is not inherited by sub sub suits": function(assert) { + assert.ok(!("foo" in assert), + "assertion function `foo` is not defined"); + } + } + } +}; + +if (module == require.main) + require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/fixtures/addon-install-unit-test@mozilla.com.xpi b/tools/addon-sdk-1.12/test/fixtures/addon-install-unit-test@mozilla.com.xpi Binary files differnew file mode 100644 index 0000000..c7cfccd --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/addon-install-unit-test@mozilla.com.xpi diff --git a/tools/addon-sdk-1.12/test/fixtures/chrome-worker/addEventListener.js b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/addEventListener.js new file mode 100644 index 0000000..a251701 --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/addEventListener.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +'use strict'; + +postMessage('Hello'); diff --git a/tools/addon-sdk-1.12/test/fixtures/chrome-worker/jsctypes.js b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/jsctypes.js new file mode 100644 index 0000000..8d5ea9a --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/jsctypes.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +'use strict'; + +postMessage(typeof ctypes.open); diff --git a/tools/addon-sdk-1.12/test/fixtures/chrome-worker/onerror.js b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/onerror.js new file mode 100644 index 0000000..7310a70 --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/onerror.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +'use strict'; + +throw new Error('ok'); diff --git a/tools/addon-sdk-1.12/test/fixtures/chrome-worker/onmessage.js b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/onmessage.js new file mode 100644 index 0000000..42962f9 --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/onmessage.js @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +'use strict'; + +onmessage = function (event) { + postMessage(event.data); +}; diff --git a/tools/addon-sdk-1.12/test/fixtures/chrome-worker/setTimeout.js b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/setTimeout.js new file mode 100644 index 0000000..1a137f1 --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/setTimeout.js @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +'use strict'; + +setTimeout(function () { + postMessage('ok'); +}, 0); diff --git a/tools/addon-sdk-1.12/test/fixtures/chrome-worker/xhr.js b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/xhr.js new file mode 100644 index 0000000..7f76289 --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/chrome-worker/xhr.js @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +'use strict'; + +let xhr = XMLHttpRequest(); +xhr.open("GET", "data:text/plain,ok", true); +xhr.onload = function () { + postMessage(xhr.responseText); +}; +xhr.send(null); diff --git a/tools/addon-sdk-1.12/test/fixtures/es5.js b/tools/addon-sdk-1.12/test/fixtures/es5.js new file mode 100644 index 0000000..746cae3 --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/es5.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; +exports.frozen = Object.freeze({}); +exports.sealed = Object.seal({}); +exports.inextensible = Object.preventExtensions({}); diff --git a/tools/addon-sdk-1.12/test/fixtures/loader/cycles/a.js b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/a.js new file mode 100644 index 0000000..f6e13cc --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/a.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +exports.b = require('b'); diff --git a/tools/addon-sdk-1.12/test/fixtures/loader/cycles/b.js b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/b.js new file mode 100644 index 0000000..69e23f1 --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/b.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +exports.a = require('a'); diff --git a/tools/addon-sdk-1.12/test/fixtures/loader/cycles/c.js b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/c.js new file mode 100644 index 0000000..ce34b3c --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/c.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +exports.main = require('main'); diff --git a/tools/addon-sdk-1.12/test/fixtures/loader/cycles/main.js b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/main.js new file mode 100644 index 0000000..6d13200 --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/loader/cycles/main.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +var a = require('a'); +var b = require('b'); +var c = require('c'); + +exports.a = a; +exports.b = b; +exports.c = c; +exports.main = exports; diff --git a/tools/addon-sdk-1.12/test/fixtures/sandbox-complex-character.js b/tools/addon-sdk-1.12/test/fixtures/sandbox-complex-character.js new file mode 100644 index 0000000..6ae6769 --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/sandbox-complex-character.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var chars = 'გამარჯობა'; diff --git a/tools/addon-sdk-1.12/test/fixtures/sandbox-normal.js b/tools/addon-sdk-1.12/test/fixtures/sandbox-normal.js new file mode 100644 index 0000000..5425298 --- /dev/null +++ b/tools/addon-sdk-1.12/test/fixtures/sandbox-normal.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var a = 1; +this.b = 2; +function f() { return 4; } diff --git a/tools/addon-sdk-1.12/test/loader/fixture.js b/tools/addon-sdk-1.12/test/loader/fixture.js new file mode 100644 index 0000000..ebf91ab --- /dev/null +++ b/tools/addon-sdk-1.12/test/loader/fixture.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.foo = foo; +exports.bar = 2; +print('testing'); diff --git a/tools/addon-sdk-1.12/test/modules/add.js b/tools/addon-sdk-1.12/test/modules/add.js new file mode 100644 index 0000000..5472934 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/add.js @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define('modules/add', function () { + return function (a, b) { + return a + b; + }; +}); diff --git a/tools/addon-sdk-1.12/test/modules/async1.js b/tools/addon-sdk-1.12/test/modules/async1.js new file mode 100644 index 0000000..b7b60fd --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/async1.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define(['./traditional2', './async2'], function () { + var traditional2 = require('./traditional2'); + return { + name: 'async1', + traditional1Name: traditional2.traditional1Name, + traditional2Name: traditional2.name, + async2Name: require('./async2').name, + async2Traditional2Name: require('./async2').traditional2Name + }; +}); diff --git a/tools/addon-sdk-1.12/test/modules/async2.js b/tools/addon-sdk-1.12/test/modules/async2.js new file mode 100644 index 0000000..802fb25 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/async2.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define(['./traditional2', 'exports'], function (traditional2, exports) { + exports.name = 'async2'; + exports.traditional2Name = traditional2.name; +}); diff --git a/tools/addon-sdk-1.12/test/modules/badExportAndReturn.js b/tools/addon-sdk-1.12/test/modules/badExportAndReturn.js new file mode 100644 index 0000000..35a5359 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/badExportAndReturn.js @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This is a bad module, it asks for exports but also returns a value from +// the define defintion function. +define(['exports'], function (exports) { + return 'badExportAndReturn'; +}); + diff --git a/tools/addon-sdk-1.12/test/modules/badFirst.js b/tools/addon-sdk-1.12/test/modules/badFirst.js new file mode 100644 index 0000000..fdb03bd --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/badFirst.js @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define(['./badSecond'], function (badSecond) { + return { + name: 'badFirst' + }; +}); diff --git a/tools/addon-sdk-1.12/test/modules/badSecond.js b/tools/addon-sdk-1.12/test/modules/badSecond.js new file mode 100644 index 0000000..8321a85 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/badSecond.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var first = require('./badFirst'); + +exports.name = 'badSecond'; +exports.badFirstName = first.name; diff --git a/tools/addon-sdk-1.12/test/modules/blue.js b/tools/addon-sdk-1.12/test/modules/blue.js new file mode 100644 index 0000000..14ab149 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/blue.js @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define(function () { + return { + name: 'blue' + }; +}); diff --git a/tools/addon-sdk-1.12/test/modules/castor.js b/tools/addon-sdk-1.12/test/modules/castor.js new file mode 100644 index 0000000..9209623 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/castor.js @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define(['exports', './pollux'], function(exports, pollux) { + exports.name = 'castor'; + exports.getPolluxName = function () { + return pollux.name; + }; +}); diff --git a/tools/addon-sdk-1.12/test/modules/cheetah.js b/tools/addon-sdk-1.12/test/modules/cheetah.js new file mode 100644 index 0000000..37d12bf --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/cheetah.js @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define(function () { + return function () { + return 'cheetah'; + }; +}); diff --git a/tools/addon-sdk-1.12/test/modules/color.js b/tools/addon-sdk-1.12/test/modules/color.js new file mode 100644 index 0000000..0d946bd --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/color.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define({ + type: 'color' +}); diff --git a/tools/addon-sdk-1.12/test/modules/dupe.js b/tools/addon-sdk-1.12/test/modules/dupe.js new file mode 100644 index 0000000..f87fa55 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/dupe.js @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define({ + name: 'dupe' +}); + +// This is wrong and should not be allowed. Only one call to +// define per file. +define([], function () { + return { + name: 'dupe2' + }; +}); diff --git a/tools/addon-sdk-1.12/test/modules/dupeNested.js b/tools/addon-sdk-1.12/test/modules/dupeNested.js new file mode 100644 index 0000000..703948c --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/dupeNested.js @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +define(function () { + // This is wrong and should not be allowed. + define('dupeNested2', { + name: 'dupeNested2' + }); + + return { + name: 'dupeNested' + }; +}); diff --git a/tools/addon-sdk-1.12/test/modules/dupeSetExports.js b/tools/addon-sdk-1.12/test/modules/dupeSetExports.js new file mode 100644 index 0000000..12a1be2 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/dupeSetExports.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define({name: "dupeSetExports"}); + +// so this should cause a failure +module.setExports("no no no"); diff --git a/tools/addon-sdk-1.12/test/modules/exportsEquals.js b/tools/addon-sdk-1.12/test/modules/exportsEquals.js new file mode 100644 index 0000000..e176316 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/exportsEquals.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +module.exports = 4; diff --git a/tools/addon-sdk-1.12/test/modules/green.js b/tools/addon-sdk-1.12/test/modules/green.js new file mode 100644 index 0000000..ce2d134 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/green.js @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define('modules/green', ['./color'], function (color) { + return { + name: 'green', + parentType: color.type + }; +}); diff --git a/tools/addon-sdk-1.12/test/modules/lion.js b/tools/addon-sdk-1.12/test/modules/lion.js new file mode 100644 index 0000000..9c3ac3b --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/lion.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define(function(require) { + return 'lion'; +}); diff --git a/tools/addon-sdk-1.12/test/modules/orange.js b/tools/addon-sdk-1.12/test/modules/orange.js new file mode 100644 index 0000000..900a32b --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/orange.js @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define(['./color'], function (color) { + return { + name: 'orange', + parentType: color.type + }; +}); diff --git a/tools/addon-sdk-1.12/test/modules/pollux.js b/tools/addon-sdk-1.12/test/modules/pollux.js new file mode 100644 index 0000000..61cf66f --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/pollux.js @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define(['exports', './castor'], function(exports, castor) { + exports.name = 'pollux'; + exports.getCastorName = function () { + return castor.name; + }; +}); diff --git a/tools/addon-sdk-1.12/test/modules/red.js b/tools/addon-sdk-1.12/test/modules/red.js new file mode 100644 index 0000000..c47b8e9 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/red.js @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define(function (require) { + // comment fake-outs for require finding. + // require('bad1'); + return { + name: 'red', + parentType: require('./color').type + }; + + /* + require('bad2'); + */ +}); diff --git a/tools/addon-sdk-1.12/test/modules/setExports.js b/tools/addon-sdk-1.12/test/modules/setExports.js new file mode 100644 index 0000000..495c301 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/setExports.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +module.setExports(5); diff --git a/tools/addon-sdk-1.12/test/modules/subtract.js b/tools/addon-sdk-1.12/test/modules/subtract.js new file mode 100644 index 0000000..06e1053 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/subtract.js @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define(function () { + return function (a, b) { + return a - b; + } +}); diff --git a/tools/addon-sdk-1.12/test/modules/tiger.js b/tools/addon-sdk-1.12/test/modules/tiger.js new file mode 100644 index 0000000..e332deb --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/tiger.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +define(function (require, exports) { + exports.name = 'tiger'; + exports.type = require('modules/types/cat').type; +}); diff --git a/tools/addon-sdk-1.12/test/modules/traditional1.js b/tools/addon-sdk-1.12/test/modules/traditional1.js new file mode 100644 index 0000000..28af8ef --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/traditional1.js @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.name = 'traditional1' + +var async1 = require('./async1'); + +exports.traditional2Name = async1.traditional2Name; +exports.traditional1Name = async1.traditional1Name; +exports.async2Name = async1.async2Name; +exports.async2Traditional2Name = async1.async2Traditional2Name; diff --git a/tools/addon-sdk-1.12/test/modules/traditional2.js b/tools/addon-sdk-1.12/test/modules/traditional2.js new file mode 100644 index 0000000..67b64ee --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/traditional2.js @@ -0,0 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.name = 'traditional2'; +exports.traditional1Name = require('./traditional1').name; diff --git a/tools/addon-sdk-1.12/test/modules/types/cat.js b/tools/addon-sdk-1.12/test/modules/types/cat.js new file mode 100644 index 0000000..41513d6 --- /dev/null +++ b/tools/addon-sdk-1.12/test/modules/types/cat.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.type = 'cat'; diff --git a/tools/addon-sdk-1.12/test/pagemod-test-helpers.js b/tools/addon-sdk-1.12/test/pagemod-test-helpers.js new file mode 100644 index 0000000..c76bcb9 --- /dev/null +++ b/tools/addon-sdk-1.12/test/pagemod-test-helpers.js @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const {Cc,Ci} = require("chrome"); +const timer = require("sdk/timers"); +const xulApp = require("sdk/system/xul-app"); +const { Loader } = require("sdk/test/loader"); + +/** + * A helper function that creates a PageMod, then opens the specified URL + * and checks the effect of the page mod on 'onload' event via testCallback. + */ +exports.testPageMod = function testPageMod(test, testURL, pageModOptions, + testCallback, timeout) { + if (!xulApp.versionInRange(xulApp.platformVersion, "1.9.3a3", "*") && + !xulApp.versionInRange(xulApp.platformVersion, "1.9.2.7", "1.9.2.*")) { + test.pass("Note: not testing PageMod, as it doesn't work on this platform version"); + return null; + } + + var wm = Cc['@mozilla.org/appshell/window-mediator;1'] + .getService(Ci.nsIWindowMediator); + var browserWindow = wm.getMostRecentWindow("navigator:browser"); + if (!browserWindow) { + test.pass("page-mod tests: could not find the browser window, so " + + "will not run. Use -a firefox to run the pagemod tests.") + return null; + } + + if (timeout !== undefined) + test.waitUntilDone(timeout); + else + test.waitUntilDone(); + + let loader = Loader(module); + let pageMod = loader.require("sdk/page-mod"); + + var pageMods = [new pageMod.PageMod(opts) for each(opts in pageModOptions)]; + + var tabBrowser = browserWindow.gBrowser; + var newTab = tabBrowser.addTab(testURL); + tabBrowser.selectedTab = newTab; + var b = tabBrowser.getBrowserForTab(newTab); + + function onPageLoad() { + b.removeEventListener("load", onPageLoad, true); + // Delay callback execute as page-mod content scripts may be executed on + // load event. So page-mod actions may not be already done. + // If we delay even more contentScriptWhen:'end', we may want to modify + // this code again. + timer.setTimeout(testCallback, 0, + b.contentWindow.wrappedJSObject, + function done() { + pageMods.forEach(function(mod) mod.destroy()); + // XXX leaks reported if we don't close the tab? + tabBrowser.removeTab(newTab); + loader.unload(); + test.done(); + } + ); + } + b.addEventListener("load", onPageLoad, true); + + return pageMods; +} diff --git a/tools/addon-sdk-1.12/test/private-browsing-helper.js b/tools/addon-sdk-1.12/test/private-browsing-helper.js new file mode 100644 index 0000000..01b85e3 --- /dev/null +++ b/tools/addon-sdk-1.12/test/private-browsing-helper.js @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +let { Cc,Ci } = require('chrome'); +const { Loader } = require('sdk/test/loader'); +let { loader } = LoaderWithHookedConsole(); +const pb = loader.require('sdk/private-browsing'); +const pbUtils = loader.require('sdk/private-browsing/utils'); + +function LoaderWithHookedConsole() { + let errors = []; + let loader = Loader(module, { + console: Object.create(console, { + error: { value: function(e) { + if (!/DEPRECATED:/.test(e)) { + console.error(e); + } + }} + }) + }); + + return { + loader: loader, + errors: errors + } +} + +exports.pb = pb; +exports.pbUtils = pbUtils; +exports.LoaderWithHookedConsole = LoaderWithHookedConsole; diff --git a/tools/addon-sdk-1.12/test/tabs/test-fennec-tabs.js b/tools/addon-sdk-1.12/test/tabs/test-fennec-tabs.js new file mode 100644 index 0000000..f3cd660 --- /dev/null +++ b/tools/addon-sdk-1.12/test/tabs/test-fennec-tabs.js @@ -0,0 +1,621 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { Cc, Ci } = require('chrome'); +const { Loader } = require('sdk/test/loader'); +const timer = require('sdk/timers'); +const tabs = require('sdk/tabs'); +const windows = require('sdk/windows'); + +const tabsLen = tabs.length; +const URL = 'data:text/html;charset=utf-8,<html><head><title>#title#</title></head></html>'; +const ERR_MSG = 'Error: This method is not yet supported by Fennec'; + +function LoaderWithHookedConsole() { + let errors = []; + let loader = Loader(module, { + console: Object.create(console, { + error: { value: function(error) { + errors.push(error); + }} + }) + }); + + return { + loader: loader, + errors: errors + } +} + +// TEST: tab unloader +exports.testAutomaticDestroy = function(test) { + test.waitUntilDone(); + + let called = false; + + let loader2 = Loader(module); + let loader3 = Loader(module); + let tabs2 = loader2.require('sdk/tabs'); + let tabs3 = loader3.require('sdk/tabs'); + let tabs2Len = tabs2.length; + + tabs2.on('open', function onOpen(tab) { + test.fail("an onOpen listener was called that should not have been"); + called = true; + }); + tabs2.on('ready', function onReady(tab) { + test.fail("an onReady listener was called that should not have been"); + called = true; + }); + tabs2.on('select', function onSelect(tab) { + test.fail("an onSelect listener was called that should not have been"); + called = true; + }); + tabs2.on('close', function onClose(tab) { + test.fail("an onClose listener was called that should not have been"); + called = true; + }); + loader2.unload(); + + tabs3.on('open', function onOpen(tab) { + test.pass("an onOpen listener was called for tabs3"); + + tab.on('ready', function onReady(tab) { + test.fail("an onReady listener was called that should not have been"); + called = true; + }); + tab.on('select', function onSelect(tab) { + test.fail("an onSelect listener was called that should not have been"); + called = true; + }); + tab.on('close', function onClose(tab) { + test.fail("an onClose listener was called that should not have been"); + called = true; + }); + }); + tabs3.open(URL.replace(/#title#/, 'tabs3')); + loader3.unload(); + + // Fire a tab event and ensure that the destroyed tab is inactive + tabs.once('open', function(tab) { + test.pass('tabs.once("open") works!'); + + test.assertEqual(tabs2Len, tabs2.length, "tabs2 length was not changed"); + test.assertEqual(tabs.length, (tabs2.length+2), "tabs.length > tabs2.length"); + + tab.once('ready', function() { + test.pass('tab.once("ready") works!'); + + tab.once('close', function() { + test.pass('tab.once("close") works!'); + + timer.setTimeout(function () { + test.assert(!called, "Unloaded tab module is destroyed and inactive"); + + // end test + test.done(); + }); + }); + + tab.close(); + }); + }); + + tabs.open('data:text/html;charset=utf-8,foo'); +}; + +// TEST: tab properties +exports.testTabProperties = function(test) { + test.waitUntilDone(); + let { loader, errors } = LoaderWithHookedConsole(); + let tabs = loader.require('sdk/tabs'); + + let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>"; + let tabsLen = tabs.length; + tabs.open({ + url: url, + onReady: function(tab) { + test.assertEqual(tab.title, "foo", "title of the new tab matches"); + test.assertEqual(tab.url, url, "URL of the new tab matches"); + test.assert(tab.favicon, "favicon of the new tab is not empty"); + // TODO: remove need for this test by implementing the favicon feature + test.assertEqual(errors.length, 1, "favicon logs an error for now"); + test.assertEqual(tab.style, null, "style of the new tab matches"); + test.assertEqual(tab.index, tabsLen, "index of the new tab matches"); + test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches"); + + tab.close(function() { + loader.unload(); + + // end test + test.done(); + }); + } + }); +}; + +// TEST: tabs iterator and length property +exports.testTabsIteratorAndLength = function(test) { + test.waitUntilDone(); + + let newTabs = []; + let startCount = 0; + for each (let t in tabs) startCount++; + + test.assertEqual(startCount, tabs.length, "length property is correct"); + + let url = "data:text/html;charset=utf-8,testTabsIteratorAndLength"; + tabs.open({url: url, onOpen: function(tab) newTabs.push(tab)}); + tabs.open({url: url, onOpen: function(tab) newTabs.push(tab)}); + tabs.open({ + url: url, + onOpen: function(tab) { + let count = 0; + for each (let t in tabs) count++; + test.assertEqual(count, startCount + 3, "iterated tab count matches"); + test.assertEqual(startCount + 3, tabs.length, "iterated tab count matches length property"); + + let newTabsLength = newTabs.length; + newTabs.forEach(function(t) t.close(function() { + if (--newTabsLength > 0) return; + + tab.close(function() { + // end test + test.done(); + }); + })); + } + }); +}; + +// TEST: tab.url setter +exports.testTabLocation = function(test) { + test.waitUntilDone(); + + let url1 = "data:text/html;charset=utf-8,foo"; + let url2 = "data:text/html;charset=utf-8,bar"; + + tabs.on('ready', function onReady(tab) { + if (tab.url != url2) + return; + + tabs.removeListener('ready', onReady); + test.pass("tab loaded the correct url"); + + tab.close(function() { + // end test + test.done(); + }); + }); + + tabs.open({ + url: url1, + onOpen: function(tab) { + tab.url = url2; + } + }); +}; + +// TEST: tab.reload() +exports.testTabReload = function(test) { + test.waitUntilDone(); + + let url = "data:text/html;charset=utf-8,<!doctype%20html><title></title>"; + + tabs.open({ + url: url, + onReady: function onReady(tab) { + tab.removeListener('ready', onReady); + + tab.once( + 'ready', + function onReload() { + test.pass("the tab was loaded again"); + test.assertEqual(tab.url, url, "the tab has the same URL"); + + // end test + tab.close(function() test.done()); + } + ); + + tab.reload(); + } + }); +}; + +// TEST: tab.move() +exports.testTabMove = function(test) { + test.waitUntilDone(); + + let { loader, errors } = LoaderWithHookedConsole(); + let tabs = loader.require('sdk/tabs'); + + let url = "data:text/html;charset=utf-8,testTabMove"; + + tabs.open({ + url: url, + onOpen: function(tab1) { + test.assert(tab1.index >= 0, "opening a tab returns a tab w/ valid index"); + + tabs.open({ + url: url, + onOpen: function(tab) { + let i = tab.index; + test.assert(tab.index > tab1.index, "2nd tab has valid index"); + tab.index = 0; + test.assertEqual(tab.index, i, "tab index after move matches"); + test.assertEqual(errors.length, 1, "setting tab.index logs error"); + + // end test + tab1.close(function() tab.close(function() { + loader.unload(); + test.done(); + })); + } + }); + } + }); +}; + +// TEST: open tab with default options +exports.testTabsOpen_alt = function(test) { + test.waitUntilDone(); + + let { loader, errors } = LoaderWithHookedConsole(); + let tabs = loader.require('sdk/tabs'); + let url = "data:text/html;charset=utf-8,default"; + + tabs.open({ + url: url, + onReady: function(tab) { + test.assertEqual(tab.url, url, "URL of the new tab matches"); + test.assertEqual(tabs.activeTab, tab, "URL of active tab in the current window matches"); + test.assertEqual(tab.isPinned, false, "The new tab is not pinned"); + test.assertEqual(errors.length, 1, "isPinned logs error"); + + // end test + tab.close(function() { + loader.unload(); + test.done(); + }); + } + }); +}; + +// TEST: open pinned tab +exports.testOpenPinned_alt = function(test) { + test.waitUntilDone(); + + let { loader, errors } = LoaderWithHookedConsole(); + let tabs = loader.require('sdk/tabs'); + let url = "about:blank"; + + tabs.open({ + url: url, + isPinned: true, + onOpen: function(tab) { + test.assertEqual(tab.isPinned, false, "The new tab is pinned"); + test.assertEqual(errors.length, 2, "isPinned logs error"); + + // end test + tab.close(function() { + loader.unload(); + test.done(); + }); + } + }); +}; + +// TEST: pin/unpin opened tab +exports.testPinUnpin_alt = function(test) { + test.waitUntilDone(); + + let { loader, errors } = LoaderWithHookedConsole(); + let tabs = loader.require('sdk/tabs'); + let url = "data:text/html;charset=utf-8,default"; + + tabs.open({ + url: url, + onOpen: function(tab) { + tab.pin(); + test.assertEqual(tab.isPinned, false, "The tab was pinned correctly"); + test.assertEqual(errors.length, 2, "tab.pin() logs error"); + + tab.unpin(); + test.assertEqual(tab.isPinned, false, "The tab was unpinned correctly"); + test.assertEqual(errors.length, 4, "tab.unpin() logs error"); + + // end test + tab.close(function() { + loader.unload(); + test.done(); + }); + } + }); +}; + +// TEST: open tab in background +exports.testInBackground = function(test) { + test.waitUntilDone(); + + let activeUrl = tabs.activeTab.url; + let url = "data:text/html;charset=utf-8,background"; + let window = windows.browserWindows.activeWindow; + tabs.once('ready', function onReady(tab) { + test.assertEqual(tabs.activeTab.url, activeUrl, "URL of active tab has not changed"); + test.assertEqual(tab.url, url, "URL of the new background tab matches"); + test.assertEqual(windows.browserWindows.activeWindow, window, "a new window was not opened"); + test.assertNotEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL"); + + // end test + tab.close(function() test.done()); + }); + + tabs.open({ + url: url, + inBackground: true + }); +}; + +// TEST: open tab in new window +exports.testOpenInNewWindow = function(test) { + test.waitUntilDone(); + + let url = "data:text/html;charset=utf-8,newwindow"; + let window = windows.browserWindows.activeWindow; + + tabs.open({ + url: url, + inNewWindow: true, + onReady: function(tab) { + test.assertEqual(windows.browserWindows.length, 1, "a new window was not opened"); + test.assertEqual(windows.browserWindows.activeWindow, window, "old window is active"); + test.assertEqual(tab.url, url, "URL of the new tab matches"); + test.assertEqual(tabs.activeTab, tab, "tab is the activeTab"); + + tab.close(function() test.done()); + } + }); +}; + +// TEST: onOpen event handler +exports.testTabsEvent_onOpen = function(test) { + test.waitUntilDone(); + + let url = URL.replace('#title#', 'testTabsEvent_onOpen'); + let eventCount = 0; + + // add listener via property assignment + function listener1(tab) { + eventCount++; + }; + tabs.on('open', listener1); + + // add listener via collection add + tabs.on('open', function listener2(tab) { + test.assertEqual(++eventCount, 2, "both listeners notified"); + tabs.removeListener('open', listener1); + tabs.removeListener('open', listener2); + + // ends test + tab.close(function() test.done()); + }); + + tabs.open(url); +}; + +// TEST: onClose event handler +exports.testTabsEvent_onClose = function(test) { + test.waitUntilDone(); + + let url = "data:text/html;charset=utf-8,onclose"; + let eventCount = 0; + + // add listener via property assignment + function listener1(tab) { + eventCount++; + } + tabs.on('close', listener1); + + // add listener via collection add + tabs.on('close', function listener2(tab) { + test.assertEqual(++eventCount, 2, "both listeners notified"); + tabs.removeListener('close', listener1); + tabs.removeListener('close', listener2); + + // end test + test.done(); + }); + + tabs.on('ready', function onReady(tab) { + tabs.removeListener('ready', onReady); + tab.close(); + }); + + tabs.open(url); +}; + +// TEST: onClose event handler when a window is closed +exports.testTabsEvent_onCloseWindow = function(test) { + test.waitUntilDone(); + + let closeCount = 0, individualCloseCount = 0; + function listener() { + closeCount++; + } + tabs.on('close', listener); + + // One tab is already open with the window + let openTabs = 0; + function testCasePossiblyLoaded(tab) { + tab.close(function() { + if (++openTabs == 3) { + tabs.removeListener("close", listener); + + test.assertEqual(closeCount, 3, "Correct number of close events received"); + test.assertEqual(individualCloseCount, 3, + "Each tab with an attached onClose listener received a close " + + "event when the window was closed"); + + test.done(); + } + }); + } + + tabs.open({ + url: "data:text/html;charset=utf-8,tab2", + onOpen: testCasePossiblyLoaded, + onClose: function() individualCloseCount++ + }); + + tabs.open({ + url: "data:text/html;charset=utf-8,tab3", + onOpen: testCasePossiblyLoaded, + onClose: function() individualCloseCount++ + }); + + tabs.open({ + url: "data:text/html;charset=utf-8,tab4", + onOpen: testCasePossiblyLoaded, + onClose: function() individualCloseCount++ + }); +}; + +// TEST: onReady event handler +exports.testTabsEvent_onReady = function(test) { + test.waitUntilDone(); + + let url = "data:text/html;charset=utf-8,onready"; + let eventCount = 0; + + // add listener via property assignment + function listener1(tab) { + eventCount++; + }; + tabs.on('ready', listener1); + + // add listener via collection add + tabs.on('ready', function listener2(tab) { + test.assertEqual(++eventCount, 2, "both listeners notified"); + tabs.removeListener('ready', listener1); + tabs.removeListener('ready', listener2); + + // end test + tab.close(function() test.done()); + }); + + tabs.open(url); +}; + +// TEST: onActivate event handler +exports.testTabsEvent_onActivate = function(test) { + test.waitUntilDone(); + + let url = "data:text/html;charset=utf-8,onactivate"; + let eventCount = 0; + + // add listener via property assignment + function listener1(tab) { + eventCount++; + }; + tabs.on('activate', listener1); + + // add listener via collection add + tabs.on('activate', function listener2(tab) { + test.assertEqual(++eventCount, 2, "both listeners notified"); + test.assertEqual(tab, tabs.activeTab, 'the active tab is correct'); + tabs.removeListener('activate', listener1); + tabs.removeListener('activate', listener2); + + // end test + tab.close(function() test.done()); + }); + + tabs.open(url); +}; + +// TEST: onDeactivate event handler +exports.testTabsEvent_onDeactivate = function(test) { + test.waitUntilDone(); + + let url = "data:text/html;charset=utf-8,ondeactivate"; + let eventCount = 0; + + // add listener via property assignment + function listener1(tab) { + eventCount++; + }; + tabs.on('deactivate', listener1); + + // add listener via collection add + tabs.on('deactivate', function listener2(tab) { + test.assertEqual(++eventCount, 2, 'both listeners notified'); + test.assertNotEqual(tab, tabs.activeTab, 'the active tab is not the deactivated tab'); + tabs.removeListener('deactivate', listener1); + tabs.removeListener('deactivate', listener2); + + // end test + tab.close(function() test.done()); + }); + + tabs.on('activate', function onActivate(tab) { + tabs.removeListener('activate', onActivate); + tabs.open("data:text/html;charset=utf-8,foo"); + tab.close(); + }); + + tabs.open(url); +}; + +// TEST: per-tab event handlers +exports.testPerTabEvents = function(test) { + test.waitUntilDone(); + + let eventCount = 0; + + tabs.open({ + url: "data:text/html;charset=utf-8,foo", + onOpen: function(tab) { + // add listener via property assignment + function listener1() { + eventCount++; + }; + tab.on('ready', listener1); + + // add listener via collection add + tab.on('ready', function listener2() { + test.assertEqual(eventCount, 1, "both listeners notified"); + tab.removeListener('ready', listener1); + tab.removeListener('ready', listener2); + + // end test + tab.close(function() test.done()); + }); + } + }); +}; + +// TEST: tabs.activeTab getter +exports.testActiveTab_getter_alt = function(test) { + test.waitUntilDone(); + + let url = URL.replace("#title#", "foo"); + tabs.open({ + url: url, + onActivate: function(tab) { + test.assertEqual(tabs.activeTab.url, tab.url, 'the active tab is correct'); + + tab.once('ready', function() { + test.assertEqual(tab.url, url); + test.assertEqual(tab.title, "foo"); + + tab.close(function() { + // end test + test.done(); + }); + }); + } + }); +}; diff --git a/tools/addon-sdk-1.12/test/tabs/test-firefox-tabs.js b/tools/addon-sdk-1.12/test/tabs/test-firefox-tabs.js new file mode 100644 index 0000000..d7c822b --- /dev/null +++ b/tools/addon-sdk-1.12/test/tabs/test-firefox-tabs.js @@ -0,0 +1,982 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { Cc, Ci } = require('chrome'); +const { Loader } = require('sdk/test/loader'); +const timer = require('sdk/timers'); +const { StringBundle } = require('sdk/deprecated/app-strings'); + +// TEST: tabs.activeTab getter +exports.testActiveTab_getter = function(test) { + test.waitUntilDone(); + + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + + let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head></html>"; + require("sdk/deprecated/tab-browser").addTab( + url, + { + onLoad: function(e) { + test.assert(tabs.activeTab); + test.assertEqual(tabs.activeTab.url, url); + test.assertEqual(tabs.activeTab.title, "foo"); + closeBrowserWindow(window, function() test.done()); + } + } + ); + }); +}; + +// Bug 682681 - tab.title should never be empty +exports.testBug682681_aboutURI = function(test) { + test.waitUntilDone(); + + let tabStrings = StringBundle('chrome://browser/locale/tabbrowser.properties'); + + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + + tabs.on('ready', function onReady(tab) { + tabs.removeListener('ready', onReady); + + test.assertEqual(tab.title, + tabStrings.get('tabs.emptyTabTitle'), + "title of about: tab is not blank"); + + // end of test + closeBrowserWindow(window, function() test.done()); + }); + + // open a about: url + tabs.open({ + url: "about:blank", + inBackground: true + }); + }); +}; + +// related to Bug 682681 +exports.testTitleForDataURI = function(test) { + test.waitUntilDone(); + + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + + tabs.on('ready', function onReady(tab) { + tabs.removeListener('ready', onReady); + + test.assertEqual(tab.title, "tab", "data: title is not Connecting..."); + + // end of test + closeBrowserWindow(window, function() test.done()); + }); + + // open a about: url + tabs.open({ + url: "data:text/html;charset=utf-8,<title>tab</title>", + inBackground: true + }); + }); +}; + +// TEST: 'BrowserWindow' instance creation on tab 'activate' event +// See bug 648244: there was a infinite loop. +exports.testBrowserWindowCreationOnActivate = function(test) { + test.waitUntilDone(); + + let windows = require("sdk/windows").browserWindows; + let tabs = require("sdk/tabs"); + + let gotActivate = false; + + tabs.once('activate', function onActivate(eventTab) { + test.assert(windows.activeWindow, "Is able to fetch activeWindow"); + gotActivate = true; + }); + + openBrowserWindow(function(window, browser) { + test.assert(gotActivate, "Received activate event before openBrowserWindow's callback is called"); + closeBrowserWindow(window, function () test.done()); + }); +} + +// TEST: tab.activate() +exports.testActiveTab_setter = function(test) { + test.waitUntilDone(); + + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head></html>"; + + tabs.on('ready', function onReady(tab) { + tabs.removeListener('ready', onReady); + test.assertEqual(tabs.activeTab.url, "about:blank", "activeTab url has not changed"); + test.assertEqual(tab.url, url, "url of new background tab matches"); + tabs.on('activate', function onActivate(eventTab) { + tabs.removeListener('activate', onActivate); + test.assertEqual(tabs.activeTab.url, url, "url after activeTab setter matches"); + test.assertEqual(eventTab, tab, "event argument is the activated tab"); + test.assertEqual(eventTab, tabs.activeTab, "the tab is the active one"); + closeBrowserWindow(window, function() test.done()); + }); + tab.activate(); + }) + + tabs.open({ + url: url, + inBackground: true + }); + }); +}; + +// TEST: tab unloader +exports.testAutomaticDestroy = function(test) { + test.waitUntilDone(); + + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + + // Create a second tab instance that we will destroy + let called = false; + + let loader = Loader(module); + let tabs2 = loader.require("sdk/tabs"); + tabs2.on('open', function onOpen(tab) { + called = true; + }); + + loader.unload(); + + // Fire a tab event and ensure that the destroyed tab is inactive + tabs.once('open', function () { + timer.setTimeout(function () { + test.assert(!called, "Unloaded tab module is destroyed and inactive"); + closeBrowserWindow(window, function() test.done()); + }, 0); + }); + + tabs.open("data:text/html;charset=utf-8,foo"); + }); +}; + +// test tab properties +exports.testTabProperties = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs= require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>"; + tabs.open({ + url: url, + onReady: function(tab) { + test.assertEqual(tab.title, "foo", "title of the new tab matches"); + test.assertEqual(tab.url, url, "URL of the new tab matches"); + test.assert(tab.favicon, "favicon of the new tab is not empty"); + test.assertEqual(tab.style, null, "style of the new tab matches"); + test.assertEqual(tab.index, 1, "index of the new tab matches"); + test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches"); + closeBrowserWindow(window, function() test.done()); + } + }); + }); +}; + +// TEST: tab properties +exports.testTabContentTypeAndReload = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>"; + let urlXML = "data:text/xml;charset=utf-8,<foo>bar</foo>"; + tabs.open({ + url: url, + onReady: function(tab) { + if (tab.url === url) { + test.assertEqual(tab.contentType, "text/html"); + tab.url = urlXML; + } else { + test.assertEqual(tab.contentType, "text/xml"); + closeBrowserWindow(window, function() test.done()); + } + } + }); + }); +}; + +// TEST: tabs iterator and length property +exports.testTabsIteratorAndLength = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let startCount = 0; + for each (let t in tabs) startCount++; + test.assertEqual(startCount, tabs.length, "length property is correct"); + let url = "data:text/html;charset=utf-8,default"; + tabs.open(url); + tabs.open(url); + tabs.open({ + url: url, + onOpen: function(tab) { + let count = 0; + for each (let t in tabs) count++; + test.assertEqual(count, startCount + 3, "iterated tab count matches"); + test.assertEqual(startCount + 3, tabs.length, "iterated tab count matches length property"); + closeBrowserWindow(window, function() test.done()); + } + }); + }); +}; + +// TEST: tab.url setter +exports.testTabLocation = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let url1 = "data:text/html;charset=utf-8,foo"; + let url2 = "data:text/html;charset=utf-8,bar"; + + tabs.on('ready', function onReady(tab) { + if (tab.url != url2) + return; + tabs.removeListener('ready', onReady); + test.pass("tab.load() loaded the correct url"); + closeBrowserWindow(window, function() test.done()); + }); + + tabs.open({ + url: url1, + onOpen: function(tab) { + tab.url = url2 + } + }); + }); +}; + +// TEST: tab.close() +exports.testTabClose = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,foo"; + + test.assertNotEqual(tabs.activeTab.url, url, "tab is not the active tab"); + tabs.on('ready', function onReady(tab) { + tabs.removeListener('ready', onReady); + test.assertEqual(tabs.activeTab.url, tab.url, "tab is now the active tab"); + tab.close(function() { + closeBrowserWindow(window, function() { + test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab"); + test.done() + }); + }); + test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab"); + }); + + tabs.open(url); + }); +}; + +// TEST: tab.reload() +exports.testTabReload = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,<!doctype%20html><title></title>"; + + tabs.open({ url: url, onReady: function onReady(tab) { + tab.removeListener("ready", onReady); + + browser.addEventListener( + "load", + function onLoad() { + browser.removeEventListener("load", onLoad, true); + + browser.addEventListener( + "load", + function onReload() { + browser.removeEventListener("load", onReload, true); + test.pass("the tab was loaded again"); + test.assertEqual(tab.url, url, "the tab has the same URL"); + closeBrowserWindow(window, function() test.done()); + }, + true + ); + tab.reload(); + }, + true + ); + }}); + }); +}; + +// TEST: tab.move() +exports.testTabMove = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,foo"; + + tabs.open({ + url: url, + onOpen: function(tab) { + test.assertEqual(tab.index, 1, "tab index before move matches"); + tab.index = 0; + test.assertEqual(tab.index, 0, "tab index after move matches"); + closeBrowserWindow(window, function() test.done()); + } + }); + }); +}; + +// TEST: open tab with default options +exports.testOpen = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,default"; + tabs.open({ + url: url, + onReady: function(tab) { + test.assertEqual(tab.url, url, "URL of the new tab matches"); + test.assertEqual(window.content.location, url, "URL of active tab in the current window matches"); + test.assertEqual(tab.isPinned, false, "The new tab is not pinned"); + + closeBrowserWindow(window, function() test.done()); + } + }); + }); +}; + +// TEST: open pinned tab +exports.testOpenPinned = function(test) { + const xulApp = require("sdk/system/xul-app"); + if (xulApp.versionInRange(xulApp.platformVersion, "2.0b2", "*")) { + // test tab pinning + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,default"; + tabs.open({ + url: url, + isPinned: true, + onOpen: function(tab) { + test.assertEqual(tab.isPinned, true, "The new tab is pinned"); + closeBrowserWindow(window, function() test.done()); + } + }); + }); + } + else { + test.pass("Pinned tabs are not supported in this application."); + } +}; + +// TEST: pin/unpin opened tab +exports.testPinUnpin = function(test) { + const xulApp = require("sdk/system/xul-app"); + if (xulApp.versionInRange(xulApp.platformVersion, "2.0b2", "*")) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,default"; + tabs.open({ + url: url, + onOpen: function(tab) { + tab.pin(); + test.assertEqual(tab.isPinned, true, "The tab was pinned correctly"); + tab.unpin(); + test.assertEqual(tab.isPinned, false, "The tab was unpinned correctly"); + closeBrowserWindow(window, function() test.done()); + } + }); + }); + } + else { + test.pass("Pinned tabs are not supported in this application."); + } +}; + +// TEST: open tab in background +exports.testInBackground = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let activeUrl = tabs.activeTab.url; + let url = "data:text/html;charset=utf-8,background"; + test.assertEqual(activeWindow, window, "activeWindow matches this window"); + tabs.on('ready', function onReady(tab) { + tabs.removeListener('ready', onReady); + test.assertEqual(tabs.activeTab.url, activeUrl, "URL of active tab has not changed"); + test.assertEqual(tab.url, url, "URL of the new background tab matches"); + test.assertEqual(activeWindow, window, "a new window was not opened"); + test.assertNotEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL"); + closeBrowserWindow(window, function() test.done()); + }); + tabs.open({ + url: url, + inBackground: true + }); + }); +}; + +// TEST: open tab in new window +exports.testOpenInNewWindow = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + + let cache = []; + let windowUtils = require("sdk/deprecated/window-utils"); + let wt = new windowUtils.WindowTracker({ + onTrack: function(win) { + cache.push(win); + }, + onUntrack: function(win) { + cache.splice(cache.indexOf(win), 1) + } + }); + let startWindowCount = cache.length; + + let url = "data:text/html;charset=utf-8,newwindow"; + tabs.open({ + url: url, + inNewWindow: true, + onReady: function(tab) { + let newWindow = cache[cache.length - 1]; + test.assertEqual(cache.length, startWindowCount + 1, "a new window was opened"); + test.assertEqual(activeWindow, newWindow, "new window is active"); + test.assertEqual(tab.url, url, "URL of the new tab matches"); + test.assertEqual(newWindow.content.location, url, "URL of new tab in new window matches"); + test.assertEqual(tabs.activeTab.url, url, "URL of activeTab matches"); + for (var i in cache) cache[i] = null; + wt.unload(); + closeBrowserWindow(newWindow, function() { + closeBrowserWindow(window, function() test.done()); + }); + } + }); + }); +}; + +// TEST: onOpen event handler +exports.testTabsEvent_onOpen = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + var tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,1"; + let eventCount = 0; + + // add listener via property assignment + function listener1(tab) { + eventCount++; + }; + tabs.on('open', listener1); + + // add listener via collection add + tabs.on('open', function listener2(tab) { + test.assertEqual(++eventCount, 2, "both listeners notified"); + tabs.removeListener('open', listener1); + tabs.removeListener('open', listener2); + closeBrowserWindow(window, function() test.done()); + }); + + tabs.open(url); + }); +}; + +// TEST: onClose event handler +exports.testTabsEvent_onClose = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + var tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,onclose"; + let eventCount = 0; + + // add listener via property assignment + function listener1(tab) { + eventCount++; + } + tabs.on('close', listener1); + + // add listener via collection add + tabs.on('close', function listener2(tab) { + test.assertEqual(++eventCount, 2, "both listeners notified"); + tabs.removeListener('close', listener1); + tabs.removeListener('close', listener2); + closeBrowserWindow(window, function() test.done()); + }); + + tabs.on('ready', function onReady(tab) { + tabs.removeListener('ready', onReady); + tab.close(); + }); + + tabs.open(url); + }); +}; + +// TEST: onClose event handler when a window is closed +exports.testTabsEvent_onCloseWindow = function(test) { + test.waitUntilDone(); + + openBrowserWindow(function(window, browser) { + var tabs = require("sdk/tabs"); + + let closeCount = 0, individualCloseCount = 0; + function listener() { + closeCount++; + } + tabs.on('close', listener); + + // One tab is already open with the window + let openTabs = 1; + function testCasePossiblyLoaded() { + if (++openTabs == 4) { + beginCloseWindow(); + } + } + + tabs.open({ + url: "data:text/html;charset=utf-8,tab2", + onOpen: function() testCasePossiblyLoaded(), + onClose: function() individualCloseCount++ + }); + + tabs.open({ + url: "data:text/html;charset=utf-8,tab3", + onOpen: function() testCasePossiblyLoaded(), + onClose: function() individualCloseCount++ + }); + + tabs.open({ + url: "data:text/html;charset=utf-8,tab4", + onOpen: function() testCasePossiblyLoaded(), + onClose: function() individualCloseCount++ + }); + + function beginCloseWindow() { + closeBrowserWindow(window, function testFinished() { + tabs.removeListener("close", listener); + + test.assertEqual(closeCount, 4, "Correct number of close events received"); + test.assertEqual(individualCloseCount, 3, + "Each tab with an attached onClose listener received a close " + + "event when the window was closed"); + + test.done(); + }); + } + + }); +} + +// TEST: onReady event handler +exports.testTabsEvent_onReady = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + var tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,onready"; + let eventCount = 0; + + // add listener via property assignment + function listener1(tab) { + eventCount++; + }; + tabs.on('ready', listener1); + + // add listener via collection add + tabs.on('ready', function listener2(tab) { + test.assertEqual(++eventCount, 2, "both listeners notified"); + tabs.removeListener('ready', listener1); + tabs.removeListener('ready', listener2); + closeBrowserWindow(window, function() test.done()); + }); + + tabs.open(url); + }); +}; + +// TEST: onActivate event handler +exports.testTabsEvent_onActivate = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + var tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,onactivate"; + let eventCount = 0; + + // add listener via property assignment + function listener1(tab) { + eventCount++; + }; + tabs.on('activate', listener1); + + // add listener via collection add + tabs.on('activate', function listener2(tab) { + test.assertEqual(++eventCount, 2, "both listeners notified"); + tabs.removeListener('activate', listener1); + tabs.removeListener('activate', listener2); + closeBrowserWindow(window, function() test.done()); + }); + + tabs.open(url); + }); +}; + +// onDeactivate event handler +exports.testTabsEvent_onDeactivate = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + var tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,ondeactivate"; + let eventCount = 0; + + // add listener via property assignment + function listener1(tab) { + eventCount++; + }; + tabs.on('deactivate', listener1); + + // add listener via collection add + tabs.on('deactivate', function listener2(tab) { + test.assertEqual(++eventCount, 2, "both listeners notified"); + tabs.removeListener('deactivate', listener1); + tabs.removeListener('deactivate', listener2); + closeBrowserWindow(window, function() test.done()); + }); + + tabs.on('open', function onOpen(tab) { + tabs.removeListener('open', onOpen); + tabs.open("data:text/html;charset=utf-8,foo"); + }); + + tabs.open(url); + }); +}; + +// pinning +exports.testTabsEvent_pinning = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + var tabs = require("sdk/tabs"); + let url = "data:text/html;charset=utf-8,1"; + + tabs.on('open', function onOpen(tab) { + tabs.removeListener('open', onOpen); + tab.pin(); + }); + + tabs.on('pinned', function onPinned(tab) { + tabs.removeListener('pinned', onPinned); + test.assert(tab.isPinned, "notified tab is pinned"); + tab.unpin(); + }); + + tabs.on('unpinned', function onUnpinned(tab) { + tabs.removeListener('unpinned', onUnpinned); + test.assert(!tab.isPinned, "notified tab is not pinned"); + closeBrowserWindow(window, function() test.done()); + }); + + tabs.open(url); + }); +}; + +// TEST: per-tab event handlers +exports.testPerTabEvents = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + var tabs = require("sdk/tabs"); + let eventCount = 0; + + tabs.open({ + url: "data:text/html;charset=utf-8,foo", + onOpen: function(tab) { + // add listener via property assignment + function listener1() { + eventCount++; + }; + tab.on('ready', listener1); + + // add listener via collection add + tab.on('ready', function listener2() { + test.assertEqual(eventCount, 1, "both listeners notified"); + tab.removeListener('ready', listener1); + tab.removeListener('ready', listener2); + closeBrowserWindow(window, function() test.done()); + }); + } + }); + }); +}; + +exports.testAttachOnOpen = function (test) { + // Take care that attach has to be called on tab ready and not on tab open. + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + + tabs.open({ + url: "data:text/html;charset=utf-8,foobar", + onOpen: function (tab) { + let worker = tab.attach({ + contentScript: 'self.postMessage(document.location.href); ', + onMessage: function (msg) { + test.assertEqual(msg, "about:blank", + "Worker document url is about:blank on open"); + worker.destroy(); + closeBrowserWindow(window, function() test.done()); + } + }); + } + }); + + }); +} + +exports.testAttachOnMultipleDocuments = function (test) { + // Example of attach that process multiple tab documents + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let firstLocation = "data:text/html;charset=utf-8,foobar"; + let secondLocation = "data:text/html;charset=utf-8,bar"; + let thirdLocation = "data:text/html;charset=utf-8,fox"; + let onReadyCount = 0; + let worker1 = null; + let worker2 = null; + let detachEventCount = 0; + tabs.open({ + url: firstLocation, + onReady: function (tab) { + onReadyCount++; + if (onReadyCount == 1) { + worker1 = tab.attach({ + contentScript: 'self.on("message", ' + + ' function () self.postMessage(document.location.href)' + + ');', + onMessage: function (msg) { + test.assertEqual(msg, firstLocation, + "Worker url is equal to the 1st document"); + tab.url = secondLocation; + }, + onDetach: function () { + detachEventCount++; + test.pass("Got worker1 detach event"); + test.assertRaises(function () { + worker1.postMessage("ex-1"); + }, + /Couldn't find the worker/, + "postMessage throw because worker1 is destroyed"); + checkEnd(); + } + }); + worker1.postMessage("new-doc-1"); + } + else if (onReadyCount == 2) { + + worker2 = tab.attach({ + contentScript: 'self.on("message", ' + + ' function () self.postMessage(document.location.href)' + + ');', + onMessage: function (msg) { + test.assertEqual(msg, secondLocation, + "Worker url is equal to the 2nd document"); + tab.url = thirdLocation; + }, + onDetach: function () { + detachEventCount++; + test.pass("Got worker2 detach event"); + test.assertRaises(function () { + worker2.postMessage("ex-2"); + }, + /Couldn't find the worker/, + "postMessage throw because worker2 is destroyed"); + checkEnd(); + } + }); + worker2.postMessage("new-doc-2"); + } + else if (onReadyCount == 3) { + tab.close(); + } + } + }); + + function checkEnd() { + if (detachEventCount != 2) + return; + + test.pass("Got all detach events"); + + closeBrowserWindow(window, function() test.done()); + } + + }); +} + + +exports.testAttachWrappers = function (test) { + // Check that content script has access to wrapped values by default + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let document = "data:text/html;charset=utf-8,<script>var globalJSVar = true; " + + " document.getElementById = 3;</script>"; + let count = 0; + + tabs.open({ + url: document, + onReady: function (tab) { + let worker = tab.attach({ + contentScript: 'try {' + + ' self.postMessage(!("globalJSVar" in window));' + + ' self.postMessage(typeof window.globalJSVar == "undefined");' + + '} catch(e) {' + + ' self.postMessage(e.message);' + + '}', + onMessage: function (msg) { + test.assertEqual(msg, true, "Worker has wrapped objects ("+count+")"); + if (count++ == 1) + closeBrowserWindow(window, function() test.done()); + } + }); + } + }); + + }); +} + +/* +// We do not offer unwrapped access to DOM since bug 601295 landed +// See 660780 to track progress of unwrap feature +exports.testAttachUnwrapped = function (test) { + // Check that content script has access to unwrapped values through unsafeWindow + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + let tabs = require("sdk/tabs"); + let document = "data:text/html;charset=utf-8,<script>var globalJSVar=true;</script>"; + let count = 0; + + tabs.open({ + url: document, + onReady: function (tab) { + let worker = tab.attach({ + contentScript: 'try {' + + ' self.postMessage(unsafeWindow.globalJSVar);' + + '} catch(e) {' + + ' self.postMessage(e.message);' + + '}', + onMessage: function (msg) { + test.assertEqual(msg, true, "Worker has access to javascript content globals ("+count+")"); + closeBrowserWindow(window, function() test.done()); + } + }); + } + }); + + }); +} +*/ + +exports['test window focus changes active tab'] = function(test) { + test.waitUntilDone(); + let win1 = openBrowserWindow(function() { + let win2 = openBrowserWindow(function() { + let tabs = require("sdk/tabs"); + tabs.on("activate", function onActivate() { + tabs.removeListener("activate", onActivate); + test.pass("activate was called on windows focus change."); + closeBrowserWindow(win1, function() { + closeBrowserWindow(win2, function() { test.done(); }); + }); + }); + win1.focus(); + }, "data:text/html;charset=utf-8,test window focus changes active tab</br><h1>Window #2"); + }, "data:text/html;charset=utf-8,test window focus changes active tab</br><h1>Window #1"); +}; + +exports['test ready event on new window tab'] = function(test) { + test.waitUntilDone(); + let uri = encodeURI("data:text/html;charset=utf-8,Waiting for ready event!"); + + require("sdk/tabs").on("ready", function onReady(tab) { + if (tab.url === uri) { + require("sdk/tabs").removeListener("ready", onReady); + test.pass("ready event was emitted"); + closeBrowserWindow(window, function() { + test.done(); + }); + } + }); + + let window = openBrowserWindow(function(){}, uri); +}; +/******************* helpers *********************/ + +// Helper for getting the active window +this.__defineGetter__("activeWindow", function activeWindow() { + return Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator). + getMostRecentWindow("navigator:browser"); +}); + +// Utility function to open a new browser window. +function openBrowserWindow(callback, url) { + let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Ci.nsIWindowWatcher); + let urlString = Cc["@mozilla.org/supports-string;1"]. + createInstance(Ci.nsISupportsString); + urlString.data = url; + let window = ww.openWindow(null, "chrome://browser/content/browser.xul", + "_blank", "chrome,all,dialog=no", urlString); + + if (callback) { + window.addEventListener("load", function onLoad(event) { + if (event.target && event.target.defaultView == window) { + window.removeEventListener("load", onLoad, true); + let browsers = window.document.getElementsByTagName("tabbrowser"); + try { + timer.setTimeout(function () { + callback(window, browsers[0]); + }, 10); + } + catch (e) { + console.exception(e); + } + } + }, true); + } + + return window; +} + +// Helper for calling code at window close +function closeBrowserWindow(window, callback) { + window.addEventListener("unload", function unload() { + window.removeEventListener("unload", unload, false); + callback(); + }, false); + window.close(); +} + +// If the module doesn't support the app we're being run in, require() will +// throw. In that case, remove all tests above from exports, and add one dummy +// test that passes. +try { + require("sdk/tabs"); +} +catch (err) { + // This bug should be mentioned in the error message. + let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=560716"; + + if (err.message.indexOf(bug) < 0) + throw err; + + module.exports = { + testAppNotSupported: function (test) { + test.pass("the tabs module does not support this application."); + } + } +} diff --git a/tools/addon-sdk-1.12/test/test-addon-installer.js b/tools/addon-sdk-1.12/test/test-addon-installer.js new file mode 100644 index 0000000..e2d3b10 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-addon-installer.js @@ -0,0 +1,131 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { Cc, Ci, Cu } = require("chrome"); +const AddonInstaller = require("sdk/addon/installer"); +const observers = require("sdk/deprecated/observer-service"); +const { setTimeout } = require("sdk/timers"); +const tmp = require("sdk/test/tmp-file"); +const system = require("sdk/system"); + +const testFolderURL = module.uri.split('test-addon-installer.js')[0]; +const ADDON_URL = testFolderURL + "fixtures/addon-install-unit-test@mozilla.com.xpi"; +const ADDON_PATH = tmp.createFromURL(ADDON_URL); + +exports.testInstall = function (test) { + test.waitUntilDone(); + + // Save all events distpatched by bootstrap.js of the installed addon + let events = []; + function eventsObserver(subject, data) { + events.push(data); + } + observers.add("addon-install-unit-test", eventsObserver, false); + + // Install the test addon + AddonInstaller.install(ADDON_PATH).then( + function onInstalled(id) { + test.assertEqual(id, "addon-install-unit-test@mozilla.com", "`id` is valid"); + + // Now uninstall it + AddonInstaller.uninstall(id).then(function () { + // Ensure that bootstrap.js methods of the addon have been called + // successfully and in the right order + let expectedEvents = ["install", "startup", "shutdown", "uninstall"]; + test.assertEqual(JSON.stringify(events), + JSON.stringify(expectedEvents), + "addon's bootstrap.js functions have been called"); + + observers.remove("addon-install-unit-test", eventsObserver); + test.done(); + }); + }, + function onFailure(code) { + test.fail("Install failed: "+code); + observers.remove("addon-install-unit-test", eventsObserver); + test.done(); + } + ); +} + +exports.testFailingInstallWithInvalidPath = function (test) { + test.waitUntilDone(); + + AddonInstaller.install("invalid-path").then( + function onInstalled(id) { + test.fail("Unexpected success"); + test.done(); + }, + function onFailure(code) { + test.assertEqual(code, AddonInstaller.ERROR_FILE_ACCESS, + "Got expected error code"); + test.done(); + } + ); +} + +exports.testFailingInstallWithInvalidFile = function (test) { + test.waitUntilDone(); + + let directory = system.pathFor("ProfD"); + AddonInstaller.install(directory).then( + function onInstalled(id) { + test.fail("Unexpected success"); + test.done(); + }, + function onFailure(code) { + test.assertEqual(code, AddonInstaller.ERROR_CORRUPT_FILE, + "Got expected error code"); + test.done(); + } + ); +} + +exports.testUpdate = function (test) { + test.waitUntilDone(); + + // Save all events distpatched by bootstrap.js of the installed addon + let events = []; + let iteration = 1; + function eventsObserver(subject, data) { + events.push(data); + } + observers.add("addon-install-unit-test", eventsObserver); + + function onInstalled(id) { + let prefix = "[" + iteration + "] "; + test.assertEqual(id, "addon-install-unit-test@mozilla.com", + prefix + "`id` is valid"); + + // On 2nd and 3rd iteration, we receive uninstall events from the last + // previously installed addon + let expectedEvents = + iteration == 1 + ? ["install", "startup"] + : ["shutdown", "uninstall", "install", "startup"]; + test.assertEqual(JSON.stringify(events), + JSON.stringify(expectedEvents), + prefix + "addon's bootstrap.js functions have been called"); + + if (iteration++ < 3) { + next(); + } + else { + observers.remove("addon-install-unit-test", eventsObserver); + test.done(); + } + } + function onFailure(code) { + test.fail("Install failed: "+code); + observers.remove("addon-install-unit-test", eventsObserver); + test.done(); + } + + function next() { + events = []; + AddonInstaller.install(ADDON_PATH).then(onInstalled, onFailure); + } + + next(); +} diff --git a/tools/addon-sdk-1.12/test/test-addon-page.js b/tools/addon-sdk-1.12/test/test-addon-page.js new file mode 100644 index 0000000..cff3273 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-addon-page.js @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +const { isTabOpen, activateTab, openTab, + closeTab, getURI } = require('sdk/tabs/utils'); +const windows = require('sdk/deprecated/window-utils'); +const { Loader } = require('sdk/test/loader'); +const { setTimeout } = require('sdk/timers'); +const { is } = require('sdk/system/xul-app'); +const tabs = require('sdk/tabs'); + +let uri = require('sdk/self').data.url('index.html'); + +function isChromeVisible(window) { + let x = window.document.documentElement.getAttribute('disablechrome') + return x !== 'true'; +} + +exports['test that add-on page has no chrome'] = function(assert, done) { + let loader = Loader(module); + loader.require('addon-kit/addon-page'); + + let window = windows.activeBrowserWindow; + let tab = openTab(window, uri); + + assert.ok(isChromeVisible(window), 'chrome is visible for non addon page'); + + // need to do this in another turn to make sure event listener + // that sets property has time to do that. + setTimeout(function() { + activateTab(tab); + + assert.equal(isChromeVisible(window), is('Fennec'), 'chrome is not visible for addon page'); + + closeTab(tab); + assert.ok(isChromeVisible(window), 'chrome is visible again'); + loader.unload(); + done(); + }); +}; + +exports['test that add-on pages are closed on unload'] = function(assert, done) { + let loader = Loader(module); + loader.require('sdk/addon-page'); + + // Wait for addon page document to be loaded + tabs.once("ready", function listener(tab) { + // Ignore loading of about:blank document + if (tab.url != uri) + return; + + loader.unload(); + assert.ok(!isTabOpen(tab), 'add-on page tabs are closed on unload'); + + done(); + }, false); + + tabs.open(uri); +}; + + +require('sdk/test').run(exports); diff --git a/tools/addon-sdk-1.12/test/test-api-utils.js b/tools/addon-sdk-1.12/test/test-api-utils.js new file mode 100644 index 0000000..5ba031a --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-api-utils.js @@ -0,0 +1,224 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const apiUtils = require("sdk/deprecated/api-utils"); + +exports.testPublicConstructor = function (test) { + function PrivateCtor() {} + PrivateCtor.prototype = {}; + + let PublicCtor = apiUtils.publicConstructor(PrivateCtor); + test.assert( + PrivateCtor.prototype.isPrototypeOf(PublicCtor.prototype), + "PrivateCtor.prototype should be prototype of PublicCtor.prototype" + ); + + function testObj(useNew) { + let obj = useNew ? new PublicCtor() : PublicCtor(); + test.assert(obj instanceof PublicCtor, + "Object should be instance of PublicCtor"); + test.assert(obj instanceof PrivateCtor, + "Object should be instance of PrivateCtor"); + test.assert(PublicCtor.prototype.isPrototypeOf(obj), + "PublicCtor's prototype should be prototype of object"); + test.assertEqual(obj.constructor, PublicCtor, + "Object constructor should be PublicCtor"); + } + testObj(true); + testObj(false); +}; + +exports.testValidateOptionsEmpty = function (test) { + let val = apiUtils.validateOptions(null, {}); + assertObjsEqual(test, val, {}); + + val = apiUtils.validateOptions(null, { foo: {} }); + assertObjsEqual(test, val, {}); + + val = apiUtils.validateOptions({}, {}); + assertObjsEqual(test, val, {}); + + val = apiUtils.validateOptions({}, { foo: {} }); + assertObjsEqual(test, val, {}); +}; + +exports.testValidateOptionsNonempty = function (test) { + let val = apiUtils.validateOptions({ foo: 123 }, {}); + assertObjsEqual(test, val, {}); + + val = apiUtils.validateOptions({ foo: 123, bar: 456 }, + { foo: {}, bar: {}, baz: {} }); + assertObjsEqual(test, val, { foo: 123, bar: 456 }); +}; + +exports.testValidateOptionsMap = function (test) { + let val = apiUtils.validateOptions({ foo: 3, bar: 2 }, { + foo: { map: function (v) v * v }, + bar: { map: function (v) undefined } + }); + assertObjsEqual(test, val, { foo: 9, bar: undefined }); +}; + +exports.testValidateOptionsMapException = function (test) { + let val = apiUtils.validateOptions({ foo: 3 }, { + foo: { map: function () { throw new Error(); }} + }); + assertObjsEqual(test, val, { foo: 3 }); +}; + +exports.testValidateOptionsOk = function (test) { + let val = apiUtils.validateOptions({ foo: 3, bar: 2, baz: 1 }, { + foo: { ok: function (v) v }, + bar: { ok: function (v) v } + }); + assertObjsEqual(test, val, { foo: 3, bar: 2 }); + + test.assertRaises( + function () apiUtils.validateOptions({ foo: 2, bar: 2 }, { + bar: { ok: function (v) v > 2 } + }), + 'The option "bar" is invalid.', + "ok should raise exception on invalid option" + ); + + test.assertRaises( + function () apiUtils.validateOptions(null, { foo: { ok: function (v) v }}), + 'The option "foo" is invalid.', + "ok should raise exception on invalid option" + ); +}; + +exports.testValidateOptionsIs = function (test) { + let opts = { + array: [], + boolean: true, + func: function () {}, + nul: null, + number: 1337, + object: {}, + string: "foo", + undef1: undefined + }; + let requirements = { + array: { is: ["array"] }, + boolean: { is: ["boolean"] }, + func: { is: ["function"] }, + nul: { is: ["null"] }, + number: { is: ["number"] }, + object: { is: ["object"] }, + string: { is: ["string"] }, + undef1: { is: ["undefined"] }, + undef2: { is: ["undefined"] } + }; + let val = apiUtils.validateOptions(opts, requirements); + assertObjsEqual(test, val, opts); + + test.assertRaises( + function () apiUtils.validateOptions(null, { + foo: { is: ["object", "number"] } + }), + 'The option "foo" must be one of the following types: object, number', + "Invalid type should raise exception" + ); +}; + +exports.testValidateOptionsMapIsOk = function (test) { + let [map, is, ok] = [false, false, false]; + let val = apiUtils.validateOptions({ foo: 1337 }, { + foo: { + map: function (v) v.toString(), + is: ["string"], + ok: function (v) v.length > 0 + } + }); + assertObjsEqual(test, val, { foo: "1337" }); + + let requirements = { + foo: { + is: ["object"], + ok: function () test.fail("is should have caused us to throw by now") + } + }; + test.assertRaises( + function () apiUtils.validateOptions(null, requirements), + 'The option "foo" must be one of the following types: object', + "is should be used before ok is called" + ); +}; + +exports.testValidateOptionsErrorMsg = function (test) { + test.assertRaises( + function () apiUtils.validateOptions(null, { + foo: { ok: function (v) v, msg: "foo!" } + }), + "foo!", + "ok should raise exception with customized message" + ); +}; + +exports.testValidateMapWithMissingKey = function (test) { + let val = apiUtils.validateOptions({ }, { + foo: { + map: function (v) v || "bar" + } + }); + assertObjsEqual(test, val, { foo: "bar" }); + + val = apiUtils.validateOptions({ }, { + foo: { + map: function (v) { throw "bar" } + } + }); + assertObjsEqual(test, val, { }); +}; + +exports.testAddIterator = function testAddIterator(test) { + let obj = {}; + let keys = ["foo", "bar", "baz"]; + let vals = [1, 2, 3]; + let keysVals = [["foo", 1], ["bar", 2], ["baz", 3]]; + apiUtils.addIterator( + obj, + function keysValsGen() { + for each (let keyVal in keysVals) + yield keyVal; + } + ); + + let keysItr = []; + for (let key in obj) + keysItr.push(key); + test.assertEqual(keysItr.length, keys.length, + "the keys iterator returns the correct number of items"); + for (let i = 0; i < keys.length; i++) + test.assertEqual(keysItr[i], keys[i], "the key is correct"); + + let valsItr = []; + for each (let val in obj) + valsItr.push(val); + test.assertEqual(valsItr.length, vals.length, + "the vals iterator returns the correct number of items"); + for (let i = 0; i < vals.length; i++) + test.assertEqual(valsItr[i], vals[i], "the val is correct"); + +}; + +function assertObjsEqual(test, obj1, obj2) { + var items = 0; + for (let key in obj1) { + items++; + test.assert(key in obj2, "obj1 key should be present in obj2"); + test.assertEqual(obj2[key], obj1[key], "obj1 value should match obj2 value"); + } + for (let key in obj2) { + items++; + test.assert(key in obj1, "obj2 key should be present in obj1"); + test.assertEqual(obj1[key], obj2[key], "obj2 value should match obj1 value"); + } + if (!items) + test.assertEqual(JSON.stringify(obj1), JSON.stringify(obj2), + "obj1 should have same JSON representation as obj2"); +} diff --git a/tools/addon-sdk-1.12/test/test-app-strings.js b/tools/addon-sdk-1.12/test/test-app-strings.js new file mode 100644 index 0000000..faa52fc --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-app-strings.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { Cc,Ci } = require("chrome"); + +let StringBundle = require("sdk/deprecated/app-strings").StringBundle; +exports.testStringBundle = function(test) { + let url = "chrome://global/locale/security/caps.properties"; + + let strings = StringBundle(url); + + test.assertEqual(strings.url, url, + "'url' property contains correct URL of string bundle"); + + let appLocale = Cc["@mozilla.org/intl/nslocaleservice;1"]. + getService(Ci.nsILocaleService). + getApplicationLocale(); + + let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"]. + getService(Ci.nsIStringBundleService). + createBundle(url, appLocale); + + let (name = "Yes") { + test.assertEqual(strings.get(name), stringBundle.GetStringFromName(name), + "getting a string returns the string"); + } + + let (name = "ExtensionCapability", args = ["foo"]) { + test.assertEqual(strings.get(name, args), + stringBundle.formatStringFromName(name, args, args.length), + "getting a formatted string returns the formatted string"); + } + + test.assertRaises(function () strings.get("nonexistentString"), + "String 'nonexistentString' could not be retrieved from " + + "the bundle due to an unknown error (it doesn't exist?).", + "retrieving a nonexistent string throws an exception"); + + let a = [], b = []; + let enumerator = stringBundle.getSimpleEnumeration(); + while (enumerator.hasMoreElements()) { + let elem = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement); + a.push([elem.key, elem.value]); + } + for (let key in strings) + b.push([ key, strings.get(key) ]); + + // Sort the arrays, because we don't assume enumeration has a set order. + // Sort compares [key, val] as string "key,val", which sorts the way we want + // it to, so there is no need to provide a custom compare function. + a.sort(); + b.sort(); + + test.assertEqual(a.length, b.length, + "the iterator returns the correct number of items"); + for (let i = 0; i < a.length; i++) { + test.assertEqual(a[i][0], b[i][0], "the iterated string's name is correct"); + test.assertEqual(a[i][1], b[i][1], + "the iterated string's value is correct"); + } +}; diff --git a/tools/addon-sdk-1.12/test/test-array.js b/tools/addon-sdk-1.12/test/test-array.js new file mode 100644 index 0000000..d68891b --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-array.js @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict' + +const array = require('sdk/util/array'); + +exports.testHas = function(test) { + var testAry = [1, 2, 3]; + test.assertEqual(array.has([1, 2, 3], 1), true); + test.assertEqual(testAry.length, 3); + test.assertEqual(testAry[0], 1); + test.assertEqual(testAry[1], 2); + test.assertEqual(testAry[2], 3); + test.assertEqual(array.has(testAry, 2), true); + test.assertEqual(array.has(testAry, 3), true); + test.assertEqual(array.has(testAry, 4), false); + test.assertEqual(array.has(testAry, '1'), false); +}; +exports.testHasAny = function(test) { + var testAry = [1, 2, 3]; + test.assertEqual(array.hasAny([1, 2, 3], [1]), true); + test.assertEqual(array.hasAny([1, 2, 3], [1, 5]), true); + test.assertEqual(array.hasAny([1, 2, 3], [5, 1]), true); + test.assertEqual(array.hasAny([1, 2, 3], [5, 2]), true); + test.assertEqual(array.hasAny([1, 2, 3], [5, 3]), true); + test.assertEqual(array.hasAny([1, 2, 3], [5, 4]), false); + test.assertEqual(testAry.length, 3); + test.assertEqual(testAry[0], 1); + test.assertEqual(testAry[1], 2); + test.assertEqual(testAry[2], 3); + test.assertEqual(array.hasAny(testAry, [2]), true); + test.assertEqual(array.hasAny(testAry, [3]), true); + test.assertEqual(array.hasAny(testAry, [4]), false); + test.assertEqual(array.hasAny(testAry), false); + test.assertEqual(array.hasAny(testAry, '1'), false); +}; + +exports.testAdd = function(test) { + var testAry = [1]; + test.assertEqual(array.add(testAry, 1), false); + test.assertEqual(testAry.length, 1); + test.assertEqual(testAry[0], 1); + test.assertEqual(array.add(testAry, 2), true); + test.assertEqual(testAry.length, 2); + test.assertEqual(testAry[0], 1); + test.assertEqual(testAry[1], 2); +}; + +exports.testRemove = function(test) { + var testAry = [1, 2]; + test.assertEqual(array.remove(testAry, 3), false); + test.assertEqual(testAry.length, 2); + test.assertEqual(testAry[0], 1); + test.assertEqual(testAry[1], 2); + test.assertEqual(array.remove(testAry, 2), true); + test.assertEqual(testAry.length, 1); + test.assertEqual(testAry[0], 1); +}; + +exports.testFlatten = function(test) { + test.assertEqual(array.flatten([1, 2, 3]).length, 3); + test.assertEqual(array.flatten([1, [2, 3]]).length, 3); + test.assertEqual(array.flatten([1, [2, [3]]]).length, 3); + test.assertEqual(array.flatten([[1], [[2, [3]]]]).length, 3); +}; diff --git a/tools/addon-sdk-1.12/test/test-base64.js b/tools/addon-sdk-1.12/test/test-base64.js new file mode 100644 index 0000000..6ca7500 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-base64.js @@ -0,0 +1,75 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const base64 = require("sdk/base64"); + +const text = "Awesome!"; +const b64text = "QXdlc29tZSE="; + +const utf8text = "✓ à la mode"; +const b64utf8text = "4pyTIMOgIGxhIG1vZGU="; + +exports["test base64.encode"] = function (assert) { + assert.equal(base64.encode(text), b64text, "encode correctly") +} + +exports["test base64.decode"] = function (assert) { + assert.equal(base64.decode(b64text), text, "decode correctly") +} + +exports["test base64.encode Unicode"] = function (assert) { + + assert.equal(base64.encode(utf8text, "utf-8"), b64utf8text, + "encode correctly Unicode strings.") +} + +exports["test base64.decode Unicode"] = function (assert) { + + assert.equal(base64.decode(b64utf8text, "utf-8"), utf8text, + "decode correctly Unicode strings.") +} + +exports["test base64.encode with wrong charset"] = function (assert) { + + assert.throws(function() { + base64.encode(utf8text, "utf-16"); + }, "The charset argument can be only 'utf-8'"); + + assert.throws(function() { + base64.encode(utf8text, ""); + }, "The charset argument can be only 'utf-8'"); + + assert.throws(function() { + base64.encode(utf8text, 8); + }, "The charset argument can be only 'utf-8'"); + +} + +exports["test base64.decode with wrong charset"] = function (assert) { + + assert.throws(function() { + base64.decode(utf8text, "utf-16"); + }, "The charset argument can be only 'utf-8'"); + + assert.throws(function() { + base64.decode(utf8text, ""); + }, "The charset argument can be only 'utf-8'"); + + assert.throws(function() { + base64.decode(utf8text, 8); + }, "The charset argument can be only 'utf-8'"); + +} + +exports["test encode/decode Unicode without utf-8 as charset"] = function (assert) { + + assert.notEqual(base64.decode(base64.encode(utf8text)), utf8text, + "Unicode strings needs 'utf-8' charset" + ); + +} + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-byte-streams.js b/tools/addon-sdk-1.12/test/test-byte-streams.js new file mode 100644 index 0000000..18a105a --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-byte-streams.js @@ -0,0 +1,169 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim:set ts=2 sw=2 sts=2 et filetype=javascript + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const byteStreams = require("sdk/io/byte-streams"); +const file = require("sdk/io/file"); +const { pathFor } = require("sdk/system"); +const { Loader } = require("sdk/test/loader"); + +const STREAM_CLOSED_ERROR = "The stream is closed and cannot be used."; + +// This should match the constant of the same name in byte-streams.js. +const BUFFER_BYTE_LEN = 0x8000; + +exports.testWriteRead = function (test) { + let fname = dataFileFilename(); + + // Write a small string less than the stream's buffer size... + let str = "exports.testWriteRead data!"; + let stream = open(test, fname, true); + test.assert(!stream.closed, "stream.closed after open should be false"); + stream.write(str); + stream.close(); + test.assert(stream.closed, "Stream should be closed after stream.close"); + test.assertRaises(function () stream.write("This shouldn't be written!"), + STREAM_CLOSED_ERROR, + "stream.write after close should raise error"); + + // ... and read it. + stream = open(test, fname); + test.assertEqual(stream.read(), str, + "stream.read should return string written"); + test.assertEqual(stream.read(), "", + "stream.read at EOS should return empty string"); + stream.close(); + test.assert(stream.closed, "Stream should be closed after stream.close"); + test.assertRaises(function () stream.read(), + STREAM_CLOSED_ERROR, + "stream.read after close should raise error"); + + file.remove(fname); +}; + +// Write a big string many times the size of the stream's buffer and read it. +exports.testWriteReadBig = function (test) { + let str = ""; + let bufLen = BUFFER_BYTE_LEN; + let fileSize = bufLen * 10; + for (let i = 0; i < fileSize; i++) + str += i % 10; + let fname = dataFileFilename(); + let stream = open(test, fname, true); + stream.write(str); + stream.close(); + stream = open(test, fname); + test.assertEqual(stream.read(), str, + "stream.read should return string written"); + stream.close(); + file.remove(fname); +}; + +// The same, but write and read in chunks. +exports.testWriteReadChunks = function (test) { + let str = ""; + let bufLen = BUFFER_BYTE_LEN; + let fileSize = bufLen * 10; + for (let i = 0; i < fileSize; i++) + str += i % 10; + let fname = dataFileFilename(); + let stream = open(test, fname, true); + let i = 0; + while (i < str.length) { + // Use a chunk length that spans buffers. + let chunk = str.substr(i, bufLen + 1); + stream.write(chunk); + i += bufLen + 1; + } + stream.close(); + stream = open(test, fname); + let readStr = ""; + bufLen = BUFFER_BYTE_LEN; + let readLen = bufLen + 1; + do { + var frag = stream.read(readLen); + readStr += frag; + } while (frag); + stream.close(); + test.assertEqual(readStr, str, + "stream.write and read in chunks should work as expected"); + file.remove(fname); +}; + +exports.testReadLengths = function (test) { + let fname = dataFileFilename(); + let str = "exports.testReadLengths data!"; + let stream = open(test, fname, true); + stream.write(str); + stream.close(); + + stream = open(test, fname); + test.assertEqual(stream.read(str.length * 1000), str, + "stream.read with big byte length should return string " + + "written"); + stream.close(); + + stream = open(test, fname); + test.assertEqual(stream.read(0), "", + "string.read with zero byte length should return empty " + + "string"); + stream.close(); + + stream = open(test, fname); + test.assertEqual(stream.read(-1), "", + "string.read with negative byte length should return " + + "empty string"); + stream.close(); + + file.remove(fname); +}; + +exports.testTruncate = function (test) { + let fname = dataFileFilename(); + let str = "exports.testReadLengths data!"; + let stream = open(test, fname, true); + stream.write(str); + stream.close(); + + stream = open(test, fname); + test.assertEqual(stream.read(), str, + "stream.read should return string written"); + stream.close(); + + stream = open(test, fname, true); + stream.close(); + + stream = open(test, fname); + test.assertEqual(stream.read(), "", + "stream.read after truncate should be empty"); + stream.close(); + + file.remove(fname); +}; + +exports.testUnload = function (test) { + let loader = Loader(module); + let file = loader.require("sdk/io/file"); + + let filename = dataFileFilename("temp-b"); + let stream = file.open(filename, "wb"); + + loader.unload(); + test.assert(stream.closed, "Stream should be closed after module unload"); +}; + +// Returns the name of a file that should be used to test writing and reading. +function dataFileFilename() { + return file.join(pathFor("ProfD"), "test-byte-streams-data"); +} + +// Opens and returns the given file and ensures it's of the correct class. +function open(test, filename, forWriting) { + let stream = file.open(filename, forWriting ? "wb" : "b"); + let klass = forWriting ? "ByteWriter" : "ByteReader"; + test.assert(stream instanceof byteStreams[klass], + "Opened stream should be a " + klass); + return stream; +} diff --git a/tools/addon-sdk-1.12/test/test-chrome.js b/tools/addon-sdk-1.12/test/test-chrome.js new file mode 100644 index 0000000..697d392 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-chrome.js @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +'use strict'; + +let chrome = require('chrome'); + +const FIXTURES_URL = module.uri.substr(0, module.uri.lastIndexOf('/') + 1) + + 'fixtures/chrome-worker/' + +exports['test addEventListener'] = function(assert, done) { + let uri = FIXTURES_URL + 'addEventListener.js'; + + let worker = new chrome.ChromeWorker(uri); + worker.addEventListener('message', function(event) { + assert.equal(event.data, 'Hello', 'message received'); + worker.terminate(); + done(); + }); +}; + +exports['test onmessage'] = function(assert, done) { + let uri = FIXTURES_URL + 'onmessage.js'; + + let worker = new chrome.ChromeWorker(uri); + worker.onmessage = function(event) { + assert.equal(event.data, 'ok', 'message received'); + worker.terminate(); + done(); + }; + worker.postMessage('ok'); +}; + +exports['test setTimeout'] = function(assert, done) { + let uri = FIXTURES_URL + 'setTimeout.js'; + + let worker = new chrome.ChromeWorker(uri); + worker.onmessage = function(event) { + assert.equal(event.data, 'ok', 'setTimeout fired'); + worker.terminate(); + done(); + }; +}; + +exports['test jsctypes'] = function(assert, done) { + let uri = FIXTURES_URL + 'jsctypes.js'; + + let worker = new chrome.ChromeWorker(uri); + worker.onmessage = function(event) { + assert.equal(event.data, 'function', 'ctypes.open is a function'); + worker.terminate(); + done(); + }; +}; + +exports['test XMLHttpRequest'] = function(assert, done) { + let uri = FIXTURES_URL + 'xhr.js'; + + let worker = new chrome.ChromeWorker(uri); + worker.onmessage = function(event) { + assert.equal(event.data, 'ok', 'XMLHttpRequest works'); + worker.terminate(); + done(); + }; +}; + +exports['test onerror'] = function(assert, done) { + let uri = FIXTURES_URL + 'onerror.js'; + + let worker = new chrome.ChromeWorker(uri); + worker.onerror = function(event) { + assert.equal(event.filename, uri, 'event reports the correct uri'); + assert.equal(event.lineno, 8, 'event reports the correct line number'); + assert.equal(event.target, worker, 'event reports the correct worker'); + assert.ok(event.message.match(/ok/), + 'event contains the exception message'); + // Call preventDefault in order to avoid being displayed in JS console. + event.preventDefault(); + worker.terminate(); + done(); + }; +}; + +require('test').run(exports); diff --git a/tools/addon-sdk-1.12/test/test-clipboard.js b/tools/addon-sdk-1.12/test/test-clipboard.js new file mode 100644 index 0000000..4749747 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-clipboard.js @@ -0,0 +1,214 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Cc, Ci } = require("chrome"); + +const imageTools = Cc["@mozilla.org/image/tools;1"]. + getService(Ci.imgITools); + +const io = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + +const base64png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" + + "AABzenr0AAAASUlEQVRYhe3O0QkAIAwD0eyqe3Q993AQ3cBSUKpygfsNTy" + + "N5ugbQpK0BAADgP0BRDWXWlwEAAAAAgPsA3rzDaAAAAHgPcGrpgAnzQ2FG" + + "bWRR9AAAAABJRU5ErkJggg%3D%3D"; + +const base64jpeg = "data:image/jpeg;base64,%2F9j%2F4AAQSkZJRgABAQAAAQABAAD%2F" + + "2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCg" + + "sOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD%2F2wBDAQMDAwQDBAgEBAgQCw" + + "kLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ" + + "EBAQEBAQEBD%2FwAARCAAgACADAREAAhEBAxEB%2F8QAHwAAAQUBAQEBAQ" + + "EAAAAAAAAAAAECAwQFBgcICQoL%2F8QAtRAAAgEDAwIEAwUFBAQAAAF9AQ" + + "IDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRol" + + "JicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eX" + + "qDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJ" + + "ytLT1NXW19jZ2uHi4%2BTl5ufo6erx8vP09fb3%2BPn6%2F8QAHwEAAwEB" + + "AQEBAQEBAQAAAAAAAAECAwQFBgcICQoL%2F8QAtREAAgECBAQDBAcFBAQA" + + "AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNO" + + "El8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0" + + "dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6ws" + + "PExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3%2BPn6%2F9oADAMB" + + "AAIRAxEAPwD5Kr8kP9CwoA5f4m%2F8iRqX%2FbH%2FANHJXr5F%2FwAjCn" + + "8%2F%2FSWfnnir%2FwAkji%2F%2B4f8A6dgeD1%2BiH8bn1BX5If6FmFqW" + + "pXtveyQwzbUXGBtB7D2r9l4U4UyjMsoo4rFUeacua75pLaUktFJLZH5NxN" + + "xNmmX5pVw2Gq8sI8tlyxe8U3q03uzD8S3dxqOi3NneSeZDJs3LgDOHBHI5" + + "6gV%2BkcG%2BH%2FDmJzuhSq4e8XzfbqfyS%2FvH5rx1xTm2MyDEUa1W8X" + + "yXXLFbTi%2BkThv7B0r%2FAJ9f%2FH2%2Fxr90%2FwCIVcI%2F9An%2FAJ" + + "Uq%2FwDyZ%2FO%2F16v%2FADfgv8j0r%2FhZvgj%2FAKDf%2FktN%2FwDE" + + "V%2Fnr%2FYWYf8%2B%2Fxj%2Fmf3R%2FxFXhH%2FoL%2FwDKdX%2F5Azrv" + + "xLouo3D3lne%2BZDJja3luM4GDwRnqDX9LeH%2FBud4nhzD1aVC8Xz%2Fa" + + "h%2Fz8l%2FePx%2FinjrIMZm1WtRxF4vls%2BSa2jFdYlDUdRsp7OSKKbc" + + "7YwNpHce1fqfCvCub5bm9HFYqjywjzXfNF7xklopN7s%2BC4l4lyvMMrq4" + + "fD1bzfLZcsltJPqktkYlfsZ%2BUnBV%2FnufVnXaD%2FAMgqD%2FgX%2Fo" + + "Rr%2BxvCr%2FkkcJ%2F3E%2F8ATsz5%2FHfx5fL8kX6%2FQjkCgD%2F%2F" + + "2Q%3D%3D"; + +const canvasHTML = "data:text/html," + encodeURIComponent( + "<html>\ + <body>\ + <canvas width='32' height='32'></canvas>\ + </body>\ + </html>" +); + +function comparePixelImages(imageA, imageB, callback) { + let tabs = require("sdk/tabs"); + + tabs.open({ + url: canvasHTML, + + onReady: function onReady(tab) { + let worker = tab.attach({ + contentScript: "new " + function() { + let canvas = document.querySelector("canvas"); + let context = canvas.getContext("2d"); + + self.port.on("draw-image", function(imageURI) { + let img = new Image(); + + img.onload = function() { + context.drawImage(this, 0, 0); + + let pixels = Array.join(context.getImageData(0, 0, 32, 32).data); + self.port.emit("image-pixels", pixels); + } + + img.src = imageURI; + }); + } + }); + + let compared = ""; + + worker.port.on("image-pixels", function (pixels) { + if (!compared) { + compared = pixels; + this.emit("draw-image", imageB); + } else { + callback(compared === pixels); + tab.close() + } + }); + + worker.port.emit("draw-image", imageA); + } + }); +} + + +// Test the typical use case, setting & getting with no flavors specified +exports.testWithNoFlavor = function(test) { + var contents = "hello there"; + var flavor = "text"; + var fullFlavor = "text/unicode"; + var clip = require("sdk/clipboard"); + // Confirm we set the clipboard + test.assert(clip.set(contents)); + // Confirm flavor is set + test.assertEqual(clip.currentFlavors[0], flavor); + // Confirm we set the clipboard + test.assertEqual(clip.get(), contents); + // Confirm we can get the clipboard using the flavor + test.assertEqual(clip.get(flavor), contents); + // Confirm we can still get the clipboard using the full flavor + test.assertEqual(clip.get(fullFlavor), contents); +}; + +// Test the slightly less common case where we specify the flavor +exports.testWithFlavor = function(test) { + var contents = "<b>hello there</b>"; + var contentsText = "hello there"; + var flavor = "html"; + var fullFlavor = "text/html"; + var unicodeFlavor = "text"; + var unicodeFullFlavor = "text/unicode"; + var clip = require("sdk/clipboard"); + test.assert(clip.set(contents, flavor)); + test.assertEqual(clip.currentFlavors[0], unicodeFlavor); + test.assertEqual(clip.currentFlavors[1], flavor); + test.assertEqual(clip.get(), contentsText); + test.assertEqual(clip.get(flavor), contents); + test.assertEqual(clip.get(fullFlavor), contents); + test.assertEqual(clip.get(unicodeFlavor), contentsText); + test.assertEqual(clip.get(unicodeFullFlavor), contentsText); +}; + +// Test that the typical case still works when we specify the flavor to set +exports.testWithRedundantFlavor = function(test) { + var contents = "<b>hello there</b>"; + var flavor = "text"; + var fullFlavor = "text/unicode"; + var clip = require("sdk/clipboard"); + test.assert(clip.set(contents, flavor)); + test.assertEqual(clip.currentFlavors[0], flavor); + test.assertEqual(clip.get(), contents); + test.assertEqual(clip.get(flavor), contents); + test.assertEqual(clip.get(fullFlavor), contents); +}; + +exports.testNotInFlavor = function(test) { + var contents = "hello there"; + var flavor = "html"; + var clip = require("sdk/clipboard"); + test.assert(clip.set(contents)); + // If there's nothing on the clipboard with this flavor, should return null + test.assertEqual(clip.get(flavor), null); +}; + +exports.testSetImage = function(test) { + var clip = require("sdk/clipboard"); + var flavor = "image"; + var fullFlavor = "image/png"; + + test.assert(clip.set(base64png, flavor), "clipboard set"); + test.assertEqual(clip.currentFlavors[0], flavor, "flavor is set"); +}; + +exports.testGetImage = function(test) { + test.waitUntilDone(); + + var clip = require("sdk/clipboard"); + + clip.set(base64png, "image"); + + var contents = clip.get(); + + comparePixelImages(base64png, contents, function (areEquals) { + test.assert(areEquals, + "Image gets from clipboard equals to image sets to the clipboard"); + + test.done(); + }); +} + +exports.testSetImageTypeNotSupported = function(test) { + var clip = require("sdk/clipboard"); + var flavor = "image"; + + test.assertRaises(function () { + clip.set(base64jpeg, flavor); + }, "Invalid flavor for image/jpeg"); + +}; + +// Notice that `imageTools.decodeImageData`, used by `clipboard.set` method for +// images, write directly to the javascript console the error in case the image +// is corrupt, even if the error is catched. +// +// See: http://mxr.mozilla.org/mozilla-central/source/image/src/Decoder.cpp#136 +exports.testSetImageTypeWrongData = function(test) { + var clip = require("sdk/clipboard"); + var flavor = "image"; + + var wrongPNG = "data:image/png" + base64jpeg.substr(15); + + test.assertRaises(function () { + clip.set(wrongPNG, flavor); + }, "Unable to decode data given in a valid image."); +}; + +// TODO: Test error cases. diff --git a/tools/addon-sdk-1.12/test/test-collection.js b/tools/addon-sdk-1.12/test/test-collection.js new file mode 100644 index 0000000..cd533bf --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-collection.js @@ -0,0 +1,127 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const collection = require("sdk/util/collection"); + +exports.testAddRemove = function (test) { + let coll = new collection.Collection(); + compare(test, coll, []); + addRemove(test, coll, [], false); +}; + +exports.testAddRemoveBackingArray = function (test) { + let items = ["foo"]; + let coll = new collection.Collection(items); + compare(test, coll, items); + addRemove(test, coll, items, true); + + items = ["foo", "bar"]; + coll = new collection.Collection(items); + compare(test, coll, items); + addRemove(test, coll, items, true); +}; + +exports.testProperty = function (test) { + let obj = makeObjWithCollProp(); + compare(test, obj.coll, []); + addRemove(test, obj.coll, [], false); + + // Test single-value set. + let items = ["foo"]; + obj.coll = items[0]; + compare(test, obj.coll, items); + addRemove(test, obj.coll, items, false); + + // Test array set. + items = ["foo", "bar"]; + obj.coll = items; + compare(test, obj.coll, items); + addRemove(test, obj.coll, items, false); +}; + +exports.testPropertyBackingArray = function (test) { + let items = ["foo"]; + let obj = makeObjWithCollProp(items); + compare(test, obj.coll, items); + addRemove(test, obj.coll, items, true); + + items = ["foo", "bar"]; + obj = makeObjWithCollProp(items); + compare(test, obj.coll, items); + addRemove(test, obj.coll, items, true); +}; + +// Adds some values to coll and then removes them. initialItems is an array +// containing coll's initial items. isBacking is true if initialItems is coll's +// backing array; the point is that updates to coll should affect initialItems +// if that's the case. +function addRemove(test, coll, initialItems, isBacking) { + let items = isBacking ? initialItems : initialItems.slice(0); + let numInitialItems = items.length; + + // Test add(val). + let numInsertions = 5; + for (let i = 0; i < numInsertions; i++) { + compare(test, coll, items); + coll.add(i); + if (!isBacking) + items.push(i); + } + compare(test, coll, items); + + // Add the items we just added to make sure duplicates aren't added. + for (let i = 0; i < numInsertions; i++) + coll.add(i); + compare(test, coll, items); + + // Test remove(val). Do a kind of shuffled remove. Remove item 1, then + // item 0, 3, 2, 5, 4, ... + for (let i = 0; i < numInsertions; i++) { + let val = i % 2 ? i - 1 : + i === numInsertions - 1 ? i : i + 1; + coll.remove(val); + if (!isBacking) + items.splice(items.indexOf(val), 1); + compare(test, coll, items); + } + test.assertEqual(coll.length, numInitialItems, + "All inserted items should be removed"); + + // Remove the items we just removed. coll should be unchanged. + for (let i = 0; i < numInsertions; i++) + coll.remove(i); + compare(test, coll, items); + + // Test add and remove([val1, val2]). + let newItems = [0, 1]; + coll.add(newItems); + compare(test, coll, isBacking ? items : items.concat(newItems)); + coll.remove(newItems); + compare(test, coll, items); + test.assertEqual(coll.length, numInitialItems, + "All inserted items should be removed"); +} + +// Asserts that the items in coll are the items of array. +function compare(test, coll, array) { + test.assertEqual(coll.length, array.length, + "Collection length should be correct"); + let numItems = 0; + for (let item in coll) { + test.assertEqual(item, array[numItems], "Items should be equal"); + numItems++; + } + test.assertEqual(numItems, array.length, + "Number of items in iteration should be correct"); +} + +// Returns a new object with a collection property named "coll". backingArray, +// if defined, will create the collection with that backing array. +function makeObjWithCollProp(backingArray) { + let obj = {}; + collection.addCollectionProperty(obj, "coll", backingArray); + return obj; +} diff --git a/tools/addon-sdk-1.12/test/test-commonjs-test-adapter.js b/tools/addon-sdk-1.12/test/test-commonjs-test-adapter.js new file mode 100644 index 0000000..936bea9 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-commonjs-test-adapter.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +exports["test custom `Assert`'s"] = require("./commonjs-test-adapter/asserts"); + +// Disabling this check since it is not yet supported by jetpack. +// if (module == require.main) + require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-content-loader.js b/tools/addon-sdk-1.12/test/test-content-loader.js new file mode 100644 index 0000000..1804ac9 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-content-loader.js @@ -0,0 +1,226 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; +const { Loader } = require('sdk/content/loader'); +const self = require("sdk/self"); + +exports['test:contentURL'] = function(test) { + let loader = Loader(), + value, emitted = 0, changes = 0; + + test.assertRaises( + function() loader.contentURL = undefined, + 'The `contentURL` option must be a valid URL.', + 'Must throw an exception if `contentURL` is not URL.' + ); + test.assertRaises( + function() loader.contentURL = null, + 'The `contentURL` option must be a valid URL.', + 'Must throw an exception if `contentURL` is not URL.' + ); + test.assertRaises( + function() loader.contentURL = 4, + 'The `contentURL` option must be a valid URL.', + 'Must throw an exception if `contentURL` is not URL.' + ); + test.assertRaises( + function() loader.contentURL = { toString: function() 'Oops' }, + 'The `contentURL` option must be a valid URL.', + 'Must throw an exception if `contentURL` is not URL.' + ); + + function listener(e) { + emitted ++; + test.assert( + 'contentURL' in e, + 'emitted event must contain "content" property' + ); + test.assert( + value, + '' + e.contentURL, + 'content property of an event must match value' + ); + } + loader.on('propertyChange', listener); + + test.assertEqual( + null, + loader.contentURL, + 'default value is `null`' + ); + loader.contentURL = value = 'data:text/html,<html><body>Hi</body><html>'; + test.assertEqual( + value, + '' + loader.contentURL, + 'data uri is ok' + ); + test.assertEqual( + ++changes, + emitted, + 'had to emit `propertyChange`' + ); + loader.contentURL = value; + test.assertEqual( + changes, + emitted, + 'must not emit `propertyChange` if same value is set' + ); + + loader.contentURL = value = 'http://google.com/'; + test.assertEqual( + value, + '' + loader.contentURL, + 'value must be set' + ); + test.assertEqual( + ++ changes, + emitted, + 'had to emit `propertyChange`' + ); + loader.contentURL = value; + test.assertEqual( + changes, + emitted, + 'must not emit `propertyChange` if same value is set' + ); + + loader.removeListener('propertyChange', listener); + loader.contentURL = value = 'about:blank'; + test.assertEqual( + value, + '' + loader.contentURL, + 'contentURL must be an actual value' + ); + test.assertEqual( + changes, + emitted, + 'listener had to be romeved' + ); +}; + +exports['test:contentScriptWhen'] = function(test) { + let loader = Loader(); + test.assertEqual( + 'end', + loader.contentScriptWhen, + '`contentScriptWhen` defaults to "end"' + ); + loader.contentScriptWhen = "end"; + test.assertEqual( + "end", + loader.contentScriptWhen + ); + try { + loader.contentScriptWhen = 'boom'; + test.fail('must throw when wrong value is set'); + } catch(e) { + test.assertEqual( + 'The `contentScriptWhen` option must be either "start", "ready" or "end".', + e.message + ); + } + loader.contentScriptWhen = null; + test.assertEqual( + 'end', + loader.contentScriptWhen, + '`contentScriptWhen` defaults to "end"' + ); + loader.contentScriptWhen = "ready"; + test.assertEqual( + "ready", + loader.contentScriptWhen + ); + loader.contentScriptWhen = "start"; + test.assertEqual( + 'start', + loader.contentScriptWhen + ); +}; + +exports['test:contentScript'] = function(test) { + let loader = Loader(), value; + test.assertEqual( + null, + loader.contentScript, + '`contentScript` defaults to `null`' + ); + loader.contentScript = value = 'let test = {};'; + test.assertEqual( + value, + loader.contentScript + ); + try { + loader.contentScript = { 1: value } + test.fail('must throw when wrong value is set'); + } catch(e) { + test.assertEqual( + 'The `contentScript` option must be a string or an array of strings.', + e.message + ); + } + try { + loader.contentScript = ['oue', 2] + test.fail('must throw when wrong value is set'); + } catch(e) { + test.assertEqual( + 'The `contentScript` option must be a string or an array of strings.', + e.message + ); + } + loader.contentScript = undefined; + test.assertEqual( + null, + loader.contentScript + ); + loader.contentScript = value = ["1;", "2;"]; + test.assertEqual( + value, + loader.contentScript + ); +}; + +exports['test:contentScriptFile'] = function(test) { + let loader = Loader(), value, uri = self.data.url("test-content-loader.js"); + test.assertEqual( + null, + loader.contentScriptFile, + '`contentScriptFile` defaults to `null`' + ); + loader.contentScriptFile = value = uri; + test.assertEqual( + value, + loader.contentScriptFile + ); + try { + loader.contentScriptFile = { 1: uri } + test.fail('must throw when wrong value is set'); + } catch(e) { + test.assertEqual( + 'The `contentScriptFile` option must be a local URL or an array of URLs.', + e.message + ); + } + + try { + loader.contentScriptFile = [ 'oue', uri ] + test.fail('must throw when wrong value is set'); + } catch(e) { + test.assertEqual( + 'The `contentScriptFile` option must be a local URL or an array of URLs.', + e.message + ); + } + + loader.contentScriptFile = undefined; + test.assertEqual( + null, + loader.contentScriptFile + ); + loader.contentScriptFile = value = [uri]; + test.assertEqual( + value, + loader.contentScriptFile + ); +}; diff --git a/tools/addon-sdk-1.12/test/test-content-proxy.js b/tools/addon-sdk-1.12/test/test-content-proxy.js new file mode 100644 index 0000000..b447df9 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-content-proxy.js @@ -0,0 +1,915 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const hiddenFrames = require("sdk/frame/hidden-frame"); +const xulApp = require("sdk/system/xul-app"); + +const USE_JS_PROXIES = !xulApp.versionInRange(xulApp.platformVersion, + "17.0a2", "*"); + +const { Loader } = require('sdk/test/loader'); + +/* + * Utility function that allow to easily run a proxy test with a clean + * new HTML document. See first unit test for usage. + */ +function createProxyTest(html, callback) { + return function (test) { + test.waitUntilDone(); + + let url = 'data:text/html;charset=utf-8,' + encodeURI(html); + + let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({ + onReady: function () { + + function onDOMReady() { + hiddenFrame.element.removeEventListener("DOMContentLoaded", onDOMReady, + false); + + let xrayWindow = hiddenFrame.element.contentWindow; + let rawWindow = xrayWindow.wrappedJSObject; + + let done = false; + let helper = { + xrayWindow: xrayWindow, + rawWindow: rawWindow, + createWorker: function (contentScript) { + return createWorker(test, xrayWindow, contentScript, helper.done); + }, + done: function () { + if (done) + return; + done = true; + hiddenFrames.remove(hiddenFrame); + test.done(); + } + } + callback(helper, test); + } + + hiddenFrame.element.addEventListener("DOMContentLoaded", onDOMReady, false); + hiddenFrame.element.setAttribute("src", url); + + } + })); + }; +} + +function createWorker(test, xrayWindow, contentScript, done) { + // We have to use Sandboxed loader in order to get access to the private + // unlock key `PRIVATE_KEY`. This key should not be used anywhere else. + // See `PRIVATE_KEY` definition in worker.js + let loader = Loader(module); + let Worker = loader.require("sdk/content/worker").Worker; + let key = loader.sandbox("sdk/content/worker").PRIVATE_KEY; + let worker = Worker({ + exposeUnlockKey : USE_JS_PROXIES ? key : null, + window: xrayWindow, + contentScript: [ + 'new ' + function () { + assert = function assert(v, msg) { + self.port.emit("assert", {assertion:v, msg:msg}); + } + done = function done() { + self.port.emit("done"); + } + }, + contentScript + ] + }); + + worker.port.on("done", done); + worker.port.on("assert", function (data) { + test.assert(data.assertion, data.msg); + }); + + return worker; +} + +/* Examples for the `createProxyTest` uses */ + +let html = "<script>var documentGlobal = true</script>"; +exports.testCreateProxyTest = createProxyTest(html, function (helper, test) { + // You can get access to regular `test` object in second argument of + // `createProxyTest` method: + test.assert(helper.rawWindow.documentGlobal, + "You have access to a raw window reference via `helper.rawWindow`"); + test.assert(!("documentGlobal" in helper.xrayWindow), + "You have access to an XrayWrapper reference via `helper.xrayWindow`"); + + // If you do not create a Worker, you have to call helper.done(), + // in order to say when your test is finished + helper.done(); +}); + +exports.testCreateProxyTestWithWorker = createProxyTest("", function (helper) { + + helper.createWorker( + "new " + function WorkerScope() { + assert(true, "You can do assertions in your content script"); + // And if you create a worker, you either have to call `done` + // from content script or helper.done() + done(); + } + ); + +}); + +exports.testCreateProxyTestWithEvents = createProxyTest("", function (helper, test) { + + let worker = helper.createWorker( + "new " + function WorkerScope() { + self.port.emit("foo"); + } + ); + + worker.port.on("foo", function () { + test.pass("You can use events"); + // And terminate your test with helper.done: + helper.done(); + }); + +}); + +if (USE_JS_PROXIES) { + // Verify that the attribute `exposeUnlockKey`, that allow this test + // to identify proxies, works correctly. + // See `PRIVATE_KEY` definition in worker.js + exports.testKeyAccess = createProxyTest("", function(helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + assert("UNWRAP_ACCESS_KEY" in window, "have access to `UNWRAP_ACCESS_KEY`"); + done(); + } + ); + + }); +} + +// Bug 714778: There was some issue around `toString` functions +// that ended up being shared between content scripts +exports.testSharedToStringProxies = createProxyTest("", function(helper) { + + let worker = helper.createWorker( + 'new ' + function ContentScriptScope() { + // We ensure that `toString` can't be modified so that nothing could + // leak to/from the document and between content scripts + // It only applies to JS proxies, there isn't any such issue with xrays. + //document.location.toString = function foo() {}; + document.location.toString.foo = "bar"; + if ('UNWRAP_ACCESS_KEY' in window) + assert(!("foo" in document.location.toString), "document.location.toString can't be modified"); + else + assert("foo" in document.location.toString, "document.location.toString can be modified"); + assert(document.location.toString() == "data:text/html;charset=utf-8,", + "First document.location.toString()"); + self.postMessage("next"); + } + ); + worker.on("message", function () { + helper.createWorker( + 'new ' + function ContentScriptScope2() { + assert(!("foo" in document.location.toString), + "document.location.toString is different for each content script"); + assert(document.location.toString() == "data:text/html;charset=utf-8,", + "Second document.location.toString()"); + done(); + } + ); + }); +}); + + +// Ensure that postMessage is working correctly across documents with an iframe +let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />'; +exports.testPostMessage = createProxyTest(html, function (helper, test) { + let ifWindow = helper.xrayWindow.document.getElementById("iframe").contentWindow; + // Listen without proxies, to check that it will work in regular case + // simulate listening from a web document. + ifWindow.addEventListener("message", function listener(event) { + ifWindow.removeEventListener("message", listener, false); + // As we are in system principal, event is an XrayWrapper + if (USE_JS_PROXIES) { + test.assertEqual(event.source, ifWindow, + "event.source is the iframe window"); + } + else { + // JS proxies had different behavior than xrays, xrays use current + // compartments when calling postMessage method. Whereas js proxies + // was using postMessage method compartment, not the caller one. + test.assertEqual(event.source, helper.xrayWindow, + "event.source is the top window"); + } + test.assertEqual(event.origin, "null", "origin is null"); + + test.assertEqual(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}", + "message data is correct"); + + helper.done(); + }, false); + + helper.createWorker( + 'new ' + function ContentScriptScope() { + assert(postMessage === postMessage, + "verify that we doesn't generate multiple functions for the same method"); + + var json = JSON.stringify({foo : "bar\n \"escaped\"."}); + + document.getElementById("iframe").contentWindow.postMessage(json, "*"); + } + ); +}); + +let html = '<input id="input2" type="checkbox" />'; +exports.testObjectListener = createProxyTest(html, function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + // Test objects being given as event listener + let input = document.getElementById("input2"); + let myClickListener = { + called: false, + handleEvent: function(event) { + assert(this === myClickListener, "`this` is the original object"); + assert(!this.called, "called only once"); + this.called = true; + if ('UNWRAP_ACCESS_KEY' in window) + assert(event.valueOf() !== event.valueOf(UNWRAP_ACCESS_KEY), "event is wrapped"); + assert(event.target, input, "event.target is the wrapped window"); + done(); + } + }; + + window.addEventListener("click", myClickListener, true); + input.click(); + window.removeEventListener("click", myClickListener, true); + } + ); + +}); + +exports.testObjectListener2 = createProxyTest("", function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + // Verify object as DOM event listener + let myMessageListener = { + called: false, + handleEvent: function(event) { + window.removeEventListener("message", myMessageListener, true); + + assert(this == myMessageListener, "`this` is the original object"); + assert(!this.called, "called only once"); + this.called = true; + if ('UNWRAP_ACCESS_KEY' in window) + assert(event.valueOf() !== event.valueOf(UNWRAP_ACCESS_KEY), "event is wrapped"); + assert(event.target == document.defaultView, "event.target is the wrapped window"); + assert(event.source == document.defaultView, "event.source is the wrapped window"); + assert(event.origin == "null", "origin is null"); + assert(event.data == "ok", "message data is correct"); + done(); + } + }; + + window.addEventListener("message", myMessageListener, true); + document.defaultView.postMessage("ok", '*'); + } + ); + +}); + +let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' + + '<input id="input2" type="checkbox" />'; + +/* Disable test to keep tree green until Bug 756214 is fixed. +exports.testStringOverload = createProxyTest(html, function (helper, test) { + // Proxy - toString error + let originalString = "string"; + let p = Proxy.create({ + get: function(receiver, name) { + if (name == "binded") + return originalString.toString.bind(originalString); + return originalString[name]; + } + }); + test.assertRaises(function () { + p.toString(); + }, + /String.prototype.toString called on incompatible Proxy/, + "toString can't be called with this being the proxy"); + test.assertEqual(p.binded(), "string", "but it works if we bind this to the original string"); + + helper.createWorker( + 'new ' + function ContentScriptScope() { + // RightJS is hacking around String.prototype, and do similar thing: + // Pass `this` from a String prototype method. + // It is funny because typeof this == "object"! + // So that when we pass `this` to a native method, + // our proxy code can fail on another even more crazy thing. + // See following test to see what fails around proxies. + String.prototype.update = function () { + assert(typeof this == "object", "in update, `this` is an object"); + assert(this.toString() == "input", "in update, `this.toString works"); + return document.querySelectorAll(this); + }; + assert("input".update().length == 3, "String.prototype overload works"); + done(); + } + ); +}); +*/ + +exports.testMozMatchedSelector = createProxyTest("", function (helper) { + helper.createWorker( + 'new ' + function ContentScriptScope() { + // Check mozMatchesSelector XrayWrappers bug: + // mozMatchesSelector returns bad results when we are not calling it from the node itself + // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers + assert(document.createElement( "div" ).mozMatchesSelector("div"), + "mozMatchesSelector works while being called from the node"); + assert(document.documentElement.mozMatchesSelector.call( + document.createElement( "div" ), + "div" + ), + "mozMatchesSelector works while being called from a " + + "function reference to " + + "document.documentElement.mozMatchesSelector.call"); + done(); + } + ); +}); + +exports.testEventsOverload = createProxyTest("", function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + // If we add a "____proxy" attribute on XrayWrappers in order to store + // the related proxy to create an unique proxy for each wrapper; + // we end up setting this attribute to prototype objects :x + // And so, instances created with such prototype will be considered + // as equal to the prototype ... + // // Internal method that return the proxy for a given XrayWrapper + // function proxify(obj) { + // if (obj._proxy) return obj._proxy; + // return obj._proxy = Proxy.create(...); + // } + // + // // Get a proxy of an XrayWrapper prototype object + // let proto = proxify(xpcProto); + // + // // Use this proxy as a prototype + // function Constr() {} + // Constr.proto = proto; + // + // // Try to create an instance using this prototype + // let xpcInstance = new Constr(); + // let wrapper = proxify(xpcInstance) + // + // xpcProto._proxy = proto and as xpcInstance.__proto__ = xpcProto, + // xpcInstance._proxy = proto ... and profixy(xpcInstance) = proto :( + // + let proto = window.document.createEvent('HTMLEvents').__proto__; + window.Event.prototype = proto; + let event = document.createEvent('HTMLEvents'); + assert(event !== proto, "Event should not be equal to its prototype"); + event.initEvent('dataavailable', true, true); + assert(event.type === 'dataavailable', "Events are working fine"); + done(); + } + ); + +}); + +exports.testNestedAttributes = createProxyTest("", function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + // XrayWrappers has a bug when you set an attribute on it, + // in some cases, it creates an unnecessary wrapper that introduces + // a different object that refers to the same original object + // Check that our wrappers don't reproduce this bug + // SEE BUG 658560: Fix identity problem with CrossOriginWrappers + let o = {sandboxObject:true}; + window.nested = o; + o.foo = true; + assert(o === window.nested, "Nested attribute to sandbox object should not be proxified"); + window.nested = document; + assert(window.nested === document, "Nested attribute to proxy should not be double proxified"); + done(); + } + ); + +}); + +exports.testFormNodeName = createProxyTest("", function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + let body = document.body; + // Check form[nodeName] + let form = document.createElement("form"); + let input = document.createElement("input"); + input.setAttribute("name", "test"); + form.appendChild(input); + body.appendChild(form); + assert(form.test == input, "form[nodeName] is valid"); + body.removeChild(form); + done(); + } + ); + +}); + +exports.testLocalStorage = createProxyTest("", function (helper, test) { + + let worker = helper.createWorker( + 'new ' + function ContentScriptScope() { + // Check localStorage: + assert(window.localStorage, "has access to localStorage"); + window.localStorage.name = 1; + assert(window.localStorage.name == 1, "localStorage appears to work"); + + self.port.on("step2", function () { + window.localStorage.clear(); + assert(window.localStorage.name == undefined, "localStorage really, really works"); + done(); + }); + self.port.emit("step1"); + } + ); + + worker.port.on("step1", function () { + test.assertEqual(helper.rawWindow.localStorage.name, 1, "localStorage really works"); + worker.port.emit("step2"); + }); + +}); + +exports.testAutoUnwrapCustomAttributes = createProxyTest("", function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + let body = document.body; + // Setting a custom object to a proxy attribute is not wrapped when we get it afterward + let object = {custom: true, enumerable: false}; + body.customAttribute = object; + if ('UNWRAP_ACCESS_KEY' in window) + assert(body.customAttribute.valueOf() === body.customAttribute.valueOf(UNWRAP_ACCESS_KEY), "custom JS attributes are not wrapped"); + assert(object === body.customAttribute, "custom JS attributes are not wrapped"); + done(); + } + ); + +}); + +exports.testObjectTag = createProxyTest("", function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + // <object>, <embed> and other tags return typeof 'function' + let flash = document.createElement("object"); + assert(typeof flash == "function", "<object> is typeof 'function'"); + assert(flash.toString().match(/\[object HTMLObjectElement.*\]/), "<object> is HTMLObjectElement"); + assert("setAttribute" in flash, "<object> has a setAttribute method"); + done(); + } + ); + +}); + +exports.testHighlightToStringBehavior = createProxyTest("", function (helper, test) { + // We do not have any workaround this particular use of toString + // applied on <object> elements. So disable this test until we found one! + //test.assertEqual(helper.rawWindow.Object.prototype.toString.call(flash), "[object HTMLObjectElement]", "<object> is HTMLObjectElement"); + function f() {}; + test.assertMatches(Object.prototype.toString.call(f), /\[object Function.*\]/, "functions are functions 1"); + // This is how jquery call toString: + test.assertMatches(helper.rawWindow.Object.prototype.toString.call(""), /\[object String.*\]/, "strings are strings"); + let o = {__exposedProps__:{}}; + test.assertMatches(helper.rawWindow.Object.prototype.toString.call(o), /\[object Object.*\]/, "objects are objects"); + + // Make sure to pass a function from the same compartments + // or toString will return [object Object] on FF8+ + let f2 = helper.rawWindow.eval("(function () {})"); + test.assertMatches(helper.rawWindow.Object.prototype.toString.call(f2), /\[object Function.*\]/, "functions are functions 2"); + + helper.done(); +}); + +exports.testDocumentTagName = createProxyTest("", function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + let body = document.body; + // Check document[tagName] + let div = document.createElement("div"); + div.setAttribute("name", "test"); + body.appendChild(div); + assert(!document.test, "document[divName] is undefined"); + body.removeChild(div); + + let form = document.createElement("form"); + form.setAttribute("name", "test"); + body.appendChild(form); + assert(document.test == form, "document[formName] is valid"); + body.removeChild(form); + + let img = document.createElement("img"); + img.setAttribute("name", "test"); + body.appendChild(img); + assert(document.test == img, "document[imgName] is valid"); + body.removeChild(img); + done(); + } + ); + +}); + +let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />'; +exports.testWindowFrames = createProxyTest(html, function (helper) { + + helper.createWorker( + 'let glob = this; new ' + function ContentScriptScope() { + // Check window[frameName] and window.frames[i] + let iframe = document.getElementById("iframe"); + //assert(window.frames.length == 1, "The iframe is reported in window.frames check1"); + //assert(window.frames[0] == iframe.contentWindow, "The iframe is reported in window.frames check2"); + //console.log(window.test+ "-"+iframe.contentWindow); + //console.log(window); + assert(window.test == iframe.contentWindow, "window[frameName] is valid"); + done(); + } + ); + +}); + +exports.testCollections = createProxyTest("", function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + // Highlight XPCNativeWrapper bug with HTMLCollection + // tds[0] is only defined on first access :o + let body = document.body; + let div = document.createElement("div"); + body.appendChild(div); + div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; + let tds = div.getElementsByTagName("td"); + assert(tds[0] == tds[0], "We can get array element multiple times"); + body.removeChild(div); + done(); + } + ); + +}); + +let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' + + '<input id="input2" type="checkbox" />'; +exports.testCollections2 = createProxyTest(html, function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + // Verify that NodeList/HTMLCollection are working fine + let body = document.body; + let inputs = body.getElementsByTagName("input"); + assert(body.childNodes.length == 3, "body.childNodes length is correct"); + assert(inputs.length == 3, "inputs.length is correct"); + assert(body.childNodes[0] == inputs[0], "body.childNodes[0] is correct"); + assert(body.childNodes[1] == inputs[1], "body.childNodes[1] is correct"); + assert(body.childNodes[2] == inputs[2], "body.childNodes[2] is correct"); + let count = 0; + for(let i in body.childNodes) { + count++; + } + // JS proxies were broken, we can iterate over some other items: + // length, item and iterator + let expectedCount; + if ('UNWRAP_ACCESS_KEY' in window) + expectedCount = 3; + else + expectedCount = 6; + assert(count == expectedCount, "body.childNodes is iterable"); + done(); + } + ); + +}); + +exports.testValueOf = createProxyTest("", function (helper) { + + if (USE_JS_PROXIES) { + helper.createWorker( + 'new ' + function ContentScriptScope() { + // Check internal use of valueOf() for JS proxies API + assert(window.valueOf().toString().match(/\[object Window.*\]/), + "proxy.valueOf() returns the wrapped version"); + assert(window.valueOf({}).toString().match(/\[object Window.*\]/), + "proxy.valueOf({}) returns the wrapped version"); + done(); + } + ); + } + else { + helper.createWorker( + 'new ' + function ContentScriptScope() { + // Bug 787013: Until this bug is fixed, we are missing some methods + // on JS objects that comes from global `Object` object + assert(!('valueOf' in window), "valueOf is missing"); + assert(!('toLocateString' in window), "toLocaleString is missing"); + done(); + } + ); + } + +}); + +exports.testXMLHttpRequest = createProxyTest("", function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + // XMLHttpRequest doesn't support XMLHttpRequest.apply, + // that may break our proxy code + assert(window.XMLHttpRequest(), "we are able to instantiate XMLHttpRequest object"); + done(); + } + ); + +}); + +exports.testXPathResult = createProxyTest("", function (helper, test) { + const XPathResultTypes = ["ANY_TYPE", + "NUMBER_TYPE", "STRING_TYPE", "BOOLEAN_TYPE", + "UNORDERED_NODE_ITERATOR_TYPE", + "ORDERED_NODE_ITERATOR_TYPE", + "UNORDERED_NODE_SNAPSHOT_TYPE", + "ORDERED_NODE_SNAPSHOT_TYPE", + "ANY_UNORDERED_NODE_TYPE", + "FIRST_ORDERED_NODE_TYPE"]; + + // Check XPathResult bug with constants being undefined on XPCNativeWrapper + let xpcXPathResult = helper.xrayWindow.XPathResult; + + XPathResultTypes.forEach(function(type, i) { + test.assertEqual(xpcXPathResult.wrappedJSObject[type], + helper.rawWindow.XPathResult[type], + "XPathResult's constants are valid on unwrapped node"); + + test.assertEqual(xpcXPathResult[type], i, + "XPathResult's constants are defined on " + + "XPCNativeWrapper (platform bug #)"); + }); + + let value = helper.rawWindow.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE; + let worker = helper.createWorker( + 'new ' + function ContentScriptScope() { + self.port.on("value", function (value) { + // Check that our work around is working: + assert(window.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE === value, + "XPathResult works correctly on Proxies"); + done(); + }); + } + ); + worker.port.emit("value", value); +}); + +exports.testPrototypeInheritance = createProxyTest("", function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + // Verify that inherited prototype function like initEvent + // are handled correctly. (e2.type will return an error if it's not the case) + let event1 = document.createEvent( 'MouseEvents' ); + event1.initEvent( "click", true, true ); + let event2 = document.createEvent( 'MouseEvents' ); + event2.initEvent( "click", true, true ); + assert(event2.type == "click", "We are able to create an event"); + done(); + } + ); + +}); + +exports.testFunctions = createProxyTest("", function (helper) { + + helper.rawWindow.callFunction = function callFunction(f) f(); + helper.rawWindow.isEqual = function isEqual(a, b) a == b; + // bug 784116: workaround in order to allow proxy code to cache proxies on + // these functions: + helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'}; + helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'}; + + helper.createWorker( + 'new ' + function ContentScriptScope() { + // Check basic usage of functions + let closure2 = function () {return "ok";}; + assert(window.wrappedJSObject.callFunction(closure2) == "ok", "Function references work"); + + // Ensure that functions are cached when being wrapped to native code + let closure = function () {}; + assert(window.wrappedJSObject.isEqual(closure, closure), "Function references are cached before being wrapped to native"); + done(); + } + ); + +}); + +let html = '<input id="input2" type="checkbox" />'; +exports.testListeners = createProxyTest(html, function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + // Verify listeners: + let input = document.getElementById("input2"); + assert(input, "proxy.getElementById works"); + + function onclick() {}; + input.onclick = onclick; + assert(input.onclick === onclick, "on* attributes are equal to original function set"); + + let addEventListenerCalled = false; + let expandoCalled = false; + input.addEventListener("click", function onclick(event) { + input.removeEventListener("click", onclick, true); + + assert(!addEventListenerCalled, "closure given to addEventListener is called once"); + if (addEventListenerCalled) + return; + addEventListenerCalled = true; + + assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals"); + if ('UNWRAP_ACCESS_KEY' in window) + assert("__isWrappedProxy" in event.target, "event object is a proxy"); + + let input2 = document.getElementById("input2"); + + input.onclick = function (event) { + input.onclick = null; + assert(!expandoCalled, "closure set to expando is called once"); + if (expandoCalled) return; + expandoCalled = true; + + assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals"); + if ('UNWRAP_ACCESS_KEY' in window) + assert("__isWrappedProxy" in event.target, "event object is a proxy"); + + setTimeout(function () { + input.click(); + done(); + }, 0); + + } + + setTimeout(function () { + input.click(); + }, 0); + + }, true); + + input.click(); + } + ); + +}); + +exports.testMozRequestAnimationFrame = createProxyTest("", function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + window.mozRequestAnimationFrame(function callback() { + assert(callback == this, "callback is equal to `this`"); + done(); + }); + } + ); + +}); + +exports.testGlobalScope = createProxyTest("", function (helper) { + + helper.createWorker( + 'let toplevelScope = true;' + + 'assert(window.toplevelScope, "variables in toplevel scope are set to `window` object");' + + 'assert(this.toplevelScope, "variables in toplevel scope are set to `this` object");' + + 'done();' + ); + +}); + +if (USE_JS_PROXIES) { + // Bug 671016: Typed arrays should not be proxified + exports.testTypedArraysX = createProxyTest("", function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + let canvas = document.createElement("canvas"); + let context = canvas.getContext("2d"); + let imageData = context.getImageData(0,0, 1, 1); + let unwrappedData; + if ('UNWRAP_ACCESS_KEY' in window) + unwrappedData = imageData.valueOf(UNWRAP_ACCESS_KEY).data + else + unwrappedData = imageData.wrappedJSObject.data; + let data = imageData.data; + dump(unwrappedData+" === "+data+"\n"); + assert(unwrappedData === data, "Typed array isn't proxified") + done(); + } + ); + + }); +} + +// Bug 715755: proxy code throw an exception on COW +// Create an http server in order to simulate real cross domain documents +exports.testCrossDomainIframe = createProxyTest("", function (helper) { + let serverPort = 8099; + let server = require("sdk/test/httpd").startServerAsync(serverPort); + server.registerPathHandler("/", function handle(request, response) { + // Returns the webpage that receive a message and forward it back to its + // parent document by appending ' world'. + let content = "<html><head><meta charset='utf-8'></head>\n"; + content += "<script>\n"; + content += " window.addEventListener('message', function (event) {\n"; + content += " parent.postMessage(event.data + ' world', '*');\n"; + content += " }, true);\n"; + content += "</script>\n"; + content += "<body></body>\n"; + content += "</html>\n"; + response.write(content); + }); + + let worker = helper.createWorker( + 'new ' + function ContentScriptScope() { + // Waits for the server page url + self.on("message", function (url) { + // Creates an iframe with this page + let iframe = document.createElement("iframe"); + iframe.addEventListener("load", function onload() { + iframe.removeEventListener("load", onload, true); + try { + // Try to communicate with iframe's content + window.addEventListener("message", function onmessage(event) { + window.removeEventListener("message", onmessage, true); + + assert(event.data == "hello world", "COW works properly"); + self.port.emit("end"); + }, true); + iframe.contentWindow.postMessage("hello", "*"); + } catch(e) { + assert(false, "COW fails : "+e.message); + } + }, true); + iframe.setAttribute("src", url); + document.body.appendChild(iframe); + }); + } + ); + + worker.port.on("end", function () { + server.stop(helper.done); + }); + + worker.postMessage("http://localhost:" + serverPort + "/"); + +}); + +// Bug 769006: Ensure that MutationObserver works fine with proxies +let html = '<a href="foo">link</a>'; +exports.testMutationObvserver = createProxyTest(html, function (helper) { + + helper.createWorker( + 'new ' + function ContentScriptScope() { + if (typeof MutationObserver == "undefined") { + assert(true, "No MutationObserver for this FF version"); + done(); + return; + } + let link = document.getElementsByTagName("a")[0]; + + // Register a Mutation observer + let obs = new MutationObserver(function(mutations){ + // Ensure that mutation data are valid + assert(mutations.length == 1, "only one attribute mutation"); + let mutation = mutations[0]; + assert(mutation.type == "attributes", "check `type`"); + assert(mutation.target == link, "check `target`"); + assert(mutation.attributeName == "href", "check `attributeName`"); + assert(mutation.oldValue == "foo", "check `oldValue`"); + obs.disconnect(); + done(); + }); + obs.observe(document, { + subtree: true, + attributes: true, + attributeOldValue: true, + attributeFilter: ["href"] + }); + + // Modify the DOM + link.setAttribute("href", "bar"); + } + ); + +}); diff --git a/tools/addon-sdk-1.12/test/test-content-symbiont.js b/tools/addon-sdk-1.12/test/test-content-symbiont.js new file mode 100644 index 0000000..470cf7e --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-content-symbiont.js @@ -0,0 +1,184 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Cc, Ci } = require('chrome'); +const { Symbiont } = require('sdk/content/symbiont'); +const self = require("sdk/self"); + +function makeWindow() { + let content = + '<?xml version="1.0"?>' + + '<window ' + + 'xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">' + + '<iframe id="content" type="content"/>' + + '</window>'; + var url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," + + encodeURIComponent(content); + var features = ["chrome", "width=10", "height=10"]; + + return Cc["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Ci.nsIWindowWatcher). + openWindow(null, url, null, features.join(","), null); +} + +exports['test:constructing symbiont && validating API'] = function(test) { + let contentScript = ["1;", "2;"]; + let contentScriptFile = self.data.url("test-content-symbiont.js"); + + // We can avoid passing a `frame` argument. Symbiont will create one + // by using HiddenFrame module + let contentSymbiont = Symbiont({ + contentScriptFile: contentScriptFile, + contentScript: contentScript, + contentScriptWhen: "start" + }); + + test.assertEqual( + contentScriptFile, + contentSymbiont.contentScriptFile, + "There is one contentScriptFile, as specified in options." + ); + test.assertEqual( + contentScript.length, + contentSymbiont.contentScript.length, + "There are two contentScripts, as specified in options." + ); + test.assertEqual( + contentScript[0], + contentSymbiont.contentScript[0], + "There are two contentScripts, as specified in options." + ); + test.assertEqual( + contentScript[1], + contentSymbiont.contentScript[1], + "There are two contentScripts, as specified in options." + ) + test.assertEqual( + contentSymbiont.contentScriptWhen, + "start", + "contentScriptWhen is as specified in options." + ); + + contentSymbiont.destroy(); +}; + +exports["test:communication with worker global scope"] = function(test) { + let window = makeWindow(); + let contentSymbiont; + + function onMessage1(message) { + test.assertEqual(message, 1, "Program gets message via onMessage."); + contentSymbiont.removeListener('message', onMessage1); + contentSymbiont.on('message', onMessage2); + contentSymbiont.postMessage(2); + }; + + function onMessage2(message) { + if (5 == message) { + test.done(); + } else { + test.assertEqual(message, 3, "Program gets message via onMessage2."); + contentSymbiont.postMessage(4) + } + } + + window.addEventListener("load", function onLoad() { + window.removeEventListener("load", onLoad, false); + let frame = window.document.getElementById("content"); + contentSymbiont = Symbiont({ + frame: frame, + contentScript: 'new ' + function() { + self.postMessage(1); + self.on("message", function onMessage(message) { + if (message === 2) + self.postMessage(3); + if (message === 4) + self.postMessage(5); + }); + } + '()', + contentScriptWhen: 'ready', + onMessage: onMessage1 + }); + + frame.setAttribute("src", "data:text/html;charset=utf-8,<html><body></body></html>"); + }, false); + test.waitUntilDone(); +}; + +exports['test:pageWorker'] = function(test) { + test.waitUntilDone(); + let worker = Symbiont({ + contentURL: 'about:buildconfig', + contentScript: 'new ' + function WorkerScope() { + self.on('message', function(data) { + if (data.valid) + self.postMessage('bye!'); + }) + self.postMessage(window.location.toString()); + }, + onMessage: function(msg) { + if (msg == 'bye!') { + test.done() + } else { + test.assertEqual( + worker.contentURL + '', + msg + ); + worker.postMessage({ valid: true }); + } + } + }); +}; + +exports["test:document element present on 'start'"] = function(test) { + test.waitUntilDone(); + let xulApp = require("sdk/system/xul-app"); + let worker = Symbiont({ + contentURL: "about:buildconfig", + contentScript: "self.postMessage(!!document.documentElement)", + contentScriptWhen: "start", + onMessage: function(message) { + if (xulApp.versionInRange(xulApp.platformVersion, "2.0b6", "*")) + test.assert(message, "document element present on 'start'"); + else + test.pass("document element not necessarily present on 'start'"); + test.done(); + } + }); +}; + +exports["test:direct communication with trusted document"] = function(test) { + test.waitUntilDone(); + + let worker = Symbiont({ + contentURL: require("sdk/self").data.url("test-trusted-document.html") + }); + + worker.port.on('document-to-addon', function (arg) { + test.assertEqual(arg, "ok", "Received an event from the document"); + worker.destroy(); + test.done(); + }); + worker.port.emit('addon-to-document', 'ok'); +}; + +exports["test:`addon` is not available when a content script is set"] = function(test) { + test.waitUntilDone(); + + let worker = Symbiont({ + contentURL: require("sdk/self").data.url("test-trusted-document.html"), + contentScript: "new " + function ContentScriptScope() { + self.port.emit("cs-to-addon", "addon" in unsafeWindow); + } + }); + + worker.port.on('cs-to-addon', function (hasAddon) { + test.assertEqual(hasAddon, false, + "`addon` is not available"); + worker.destroy(); + test.done(); + }); +}; diff --git a/tools/addon-sdk-1.12/test/test-content-worker.js b/tools/addon-sdk-1.12/test/test-content-worker.js new file mode 100644 index 0000000..f1c8983 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-content-worker.js @@ -0,0 +1,657 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use stirct"; + +const { Cc, Ci } = require("chrome"); +const { setTimeout } = require("sdk/timers"); +const { Loader, Require, override } = require("sdk/test/loader"); +const { Worker } = require("sdk/content/worker"); + +const DEFAULT_CONTENT_URL = "data:text/html;charset=utf-8,foo"; + +function makeWindow(contentURL) { + let content = + "<?xml version=\"1.0\"?>" + + "<window " + + "xmlns=\"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul\">" + + "<script>var documentValue=true;</script>" + + "</window>"; + var url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," + + encodeURIComponent(content); + var features = ["chrome", "width=10", "height=10"]; + + return Cc["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Ci.nsIWindowWatcher). + openWindow(null, url, null, features.join(","), null); +} + +// Listen for only first one occurence of DOM event +function listenOnce(node, eventName, callback) { + node.addEventListener(eventName, function onevent(event) { + node.removeEventListener(eventName, onevent, true); + callback(node); + }, true); +} + +// Load a given url in a given browser and fires the callback when it is loaded +function loadAndWait(browser, url, callback) { + listenOnce(browser, "load", callback); + // We have to wait before calling `loadURI` otherwise, if we call + // `loadAndWait` during browser load event, the history will be broken + setTimeout(function () { + browser.loadURI(url); + }, 0); +} + +// Returns a test function that will automatically open a new chrome window +// with a <browser> element loaded on a given content URL +// The callback receive 3 arguments: +// - test: reference to the jetpack test object +// - browser: a reference to the <browser> xul node +// - done: a callback to call when test is over +function WorkerTest(url, callback) { + return function testFunction(test) { + test.waitUntilDone(); + let chromeWindow = makeWindow(); + chromeWindow.addEventListener("load", function onload() { + chromeWindow.removeEventListener("load", onload, true); + let browser = chromeWindow.document.createElement("browser"); + browser.setAttribute("type", "content"); + chromeWindow.document.documentElement.appendChild(browser); + // Wait for about:blank load event ... + listenOnce(browser, "load", function onAboutBlankLoad() { + // ... before loading the expected doc and waiting for its load event + loadAndWait(browser, url, function onDocumentLoaded() { + callback(test, browser, function onTestDone() { + chromeWindow.close(); + test.done(); + }); + }); + }); + }, true); + }; +} + +exports["test:sample"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(test, browser, done) { + + test.assertNotEqual(browser.contentWindow.location.href, "about:blank", + "window is now on the right document"); + + let window = browser.contentWindow + let worker = Worker({ + window: window, + contentScript: "new " + function WorkerScope() { + // window is accessible + let myLocation = window.location.toString(); + self.on("message", function(data) { + if (data == "hi!") + self.postMessage("bye!"); + }); + }, + contentScriptWhen: "ready", + onMessage: function(msg) { + test.assertEqual("bye!", msg); + test.assertEqual(worker.url, window.location.href, + "worker.url still works"); + done(); + } + }); + + test.assertEqual(worker.url, window.location.href, + "worker.url works"); + worker.postMessage("hi!"); + } +); + +exports["test:emit"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(test, browser, done) { + + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + // Validate self.on and self.emit + self.port.on("addon-to-content", function (data) { + self.port.emit("content-to-addon", data); + }); + + // Check for global pollution + //if (typeof on != "undefined") + // self.postMessage("`on` is in globals"); + if (typeof once != "undefined") + self.postMessage("`once` is in globals"); + if (typeof emit != "undefined") + self.postMessage("`emit` is in globals"); + + }, + onMessage: function(msg) { + test.fail("Got an unexpected message : "+msg); + } + }); + + // Validate worker.port + worker.port.on("content-to-addon", function (data) { + test.assertEqual(data, "event data"); + done(); + }); + worker.port.emit("addon-to-content", "event data"); + } +); + +exports["test:emit hack message"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(test, browser, done) { + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + // Validate self.port + self.port.on("message", function (data) { + self.port.emit("message", data); + }); + // We should not receive message on self, but only on self.port + self.on("message", function (data) { + self.postMessage("message", data); + }); + }, + onError: function(e) { + test.fail("Got exception: "+e); + } + }); + + worker.port.on("message", function (data) { + test.assertEqual(data, "event data"); + done(); + }); + worker.on("message", function (data) { + test.fail("Got an unexpected message : "+msg); + }); + worker.port.emit("message", "event data"); + } +); + +exports["test:n-arguments emit"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(test, browser, done) { + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + // Validate self.on and self.emit + self.port.on("addon-to-content", function (a1, a2, a3) { + self.port.emit("content-to-addon", a1, a2, a3); + }); + } + }); + + // Validate worker.port + worker.port.on("content-to-addon", function (arg1, arg2, arg3) { + test.assertEqual(arg1, "first argument"); + test.assertEqual(arg2, "second"); + test.assertEqual(arg3, "third"); + done(); + }); + worker.port.emit("addon-to-content", "first argument", "second", "third"); + } +); + +exports["test:post-json-values-only"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(test, browser, done) { + + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + self.on("message", function (message) { + self.postMessage([ message.fun === undefined, + typeof message.w, + message.w && "port" in message.w, + message.w.url, + Array.isArray(message.array), + JSON.stringify(message.array)]); + }); + } + }); + + // Validate worker.onMessage + let array = [1, 2, 3]; + worker.on("message", function (message) { + test.assert(message[0], "function becomes undefined"); + test.assertEqual(message[1], "object", "object stays object"); + test.assert(message[2], "object's attributes are enumerable"); + test.assertEqual(message[3], DEFAULT_CONTENT_URL, + "jsonable attributes are accessible"); + // See bug 714891, Arrays may be broken over compartements: + test.assert(message[4], "Array keeps being an array"); + test.assertEqual(message[5], JSON.stringify(array), + "Array is correctly serialized"); + done(); + }); + worker.postMessage({ fun: function () {}, w: worker, array: array }); + } +); + +exports["test:emit-json-values-only"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(test, browser, done) { + + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + // Validate self.on and self.emit + self.port.on("addon-to-content", function (fun, w, obj, array) { + self.port.emit("content-to-addon", [ + fun === null, + typeof w, + "port" in w, + w.url, + "fun" in obj, + Object.keys(obj.dom).length, + Array.isArray(array), + JSON.stringify(array) + ]); + }); + } + }); + + // Validate worker.port + let array = [1, 2, 3]; + worker.port.on("content-to-addon", function (result) { + test.assert(result[0], "functions become null"); + test.assertEqual(result[1], "object", "objects stay objects"); + test.assert(result[2], "object's attributes are enumerable"); + test.assertEqual(result[3], DEFAULT_CONTENT_URL, + "json attribute is accessible"); + test.assert(!result[4], "function as object attribute is removed"); + test.assertEqual(result[5], 0, "DOM nodes are converted into empty object"); + // See bug 714891, Arrays may be broken over compartments: + test.assert(result[6], "Array keeps being an array"); + test.assertEqual(result[7], JSON.stringify(array), + "Array is correctly serialized"); + done(); + }); + + let obj = { + fun: function () {}, + dom: browser.contentWindow.document.createElement("div") + }; + worker.port.emit("addon-to-content", function () {}, worker, obj, array); + } +); + +exports["test:content is wrapped"] = WorkerTest( + "data:text/html;charset=utf-8,<script>var documentValue=true;</script>", + function(test, browser, done) { + + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + self.postMessage(!window.documentValue); + }, + contentScriptWhen: "ready", + onMessage: function(msg) { + test.assert(msg, + "content script has a wrapped access to content document"); + done(); + } + }); + } +); + +exports["test:chrome is unwrapped"] = function(test) { + let window = makeWindow(); + test.waitUntilDone(); + + listenOnce(window, "load", function onload() { + + let worker = Worker({ + window: window, + contentScript: "new " + function WorkerScope() { + self.postMessage(window.documentValue); + }, + contentScriptWhen: "ready", + onMessage: function(msg) { + test.assert(msg, + "content script has an unwrapped access to chrome document"); + window.close(); + test.done(); + } + }); + + }); +} + +exports["test:nothing is leaked to content script"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(test, browser, done) { + + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + self.postMessage([ + "ContentWorker" in window, + "UNWRAP_ACCESS_KEY" in window, + "getProxyForObject" in window + ]); + }, + contentScriptWhen: "ready", + onMessage: function(list) { + test.assert(!list[0], "worker API contrustor isn't leaked"); + test.assert(!list[1], "Proxy API stuff isn't leaked 1/2"); + test.assert(!list[2], "Proxy API stuff isn't leaked 2/2"); + done(); + } + }); + } +); + +exports["test:ensure console.xxx works in cs"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(test, browser, done) { + + // Create a new module loader in order to be able to create a `console` + // module mockup: + let loader = Loader(module, { + console: { + log: hook.bind("log"), + info: hook.bind("info"), + warn: hook.bind("warn"), + error: hook.bind("error"), + debug: hook.bind("debug"), + exception: hook.bind("exception") + } + }); + + // Intercept all console method calls + let calls = []; + function hook(msg) { + test.assertEqual(this, msg, + "console.xxx(\"xxx\"), i.e. message is equal to the " + + "console method name we are calling"); + calls.push(msg); + } + + // Finally, create a worker that will call all console methods + let worker = loader.require("sdk/content/worker").Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + console.log("log"); + console.info("info"); + console.warn("warn"); + console.error("error"); + console.debug("debug"); + console.exception("exception"); + self.postMessage(); + }, + onMessage: function() { + // Ensure that console methods are called in the same execution order + test.assertEqual(JSON.stringify(calls), + JSON.stringify(["log", "info", "warn", "error", "debug", "exception"]), + "console has been called successfully, in the expected order"); + done(); + } + }); + } +); + + +exports["test:setTimeout can\"t be cancelled by content"] = WorkerTest( + "data:text/html;charset=utf-8,<script>var documentValue=true;</script>", + function(test, browser, done) { + + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + let id = setTimeout(function () { + self.postMessage("timeout"); + }, 100); + unsafeWindow.eval("clearTimeout("+id+");"); + }, + contentScriptWhen: "ready", + onMessage: function(msg) { + test.assert(msg, + "content didn't managed to cancel our setTimeout"); + done(); + } + }); + } +); + +exports["test:clearTimeout"] = WorkerTest( + "data:text/html;charset=utf-8,clear timeout", + function(test, browser, done) { + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + let id1 = setTimeout(function() { + self.postMessage("failed"); + }, 10); + let id2 = setTimeout(function() { + self.postMessage("done"); + }, 100); + clearTimeout(id1); + }, + contentScriptWhen: "ready", + onMessage: function(msg) { + if (msg === "failed") { + test.fail("failed to cancel timer"); + } else { + test.pass("timer cancelled"); + done(); + } + } + }); + } +); + +exports["test:clearInterval"] = WorkerTest( + "data:text/html;charset=utf-8,clear timeout", + function(test, browser, done) { + let called = 0; + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + let id = setInterval(function() { + self.postMessage("intreval") + clearInterval(id) + setTimeout(function() { + self.postMessage("done") + }, 100) + }, 10); + }, + contentScriptWhen: "ready", + onMessage: function(msg) { + if (msg === "intreval") { + called = called + 1; + if (called > 1) test.fail("failed to cancel timer"); + } else { + test.pass("interval cancelled"); + done(); + } + } + }); + } +) + +exports["test:setTimeout are unregistered on content unload"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(test, browser, done) { + + let originalWindow = browser.contentWindow; + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + document.title = "ok"; + let i = 0; + setInterval(function () { + document.title = i++; + }, 10); + }, + contentScriptWhen: "ready" + }); + + // Change location so that content script is destroyed, + // and all setTimeout/setInterval should be unregistered. + // Wait some cycles in order to execute some intervals. + setTimeout(function () { + // Bug 689621: Wait for the new document load so that we are sure that + // previous document cancelled its intervals + let url2 = "data:text/html;charset=utf-8,<title>final</title>"; + loadAndWait(browser, url2, function onload() { + let titleAfterLoad = originalWindow.document.title; + // Wait additional cycles to verify that intervals are really cancelled + setTimeout(function () { + test.assertEqual(browser.contentDocument.title, "final", + "New document has not been modified"); + test.assertEqual(originalWindow.document.title, titleAfterLoad, + "Nor previous one"); + + done(); + }, 100); + }); + }, 100); + } +); + +exports['test:check window attribute in iframes'] = WorkerTest( + DEFAULT_CONTENT_URL, + function(test, browser, done) { + + // Create a first iframe and wait for its loading + let contentWin = browser.contentWindow; + let contentDoc = contentWin.document; + let iframe = contentDoc.createElement("iframe"); + contentDoc.body.appendChild(iframe); + + listenOnce(iframe, "load", function onload() { + + // Create a second iframe inside the first one and wait for its loading + let iframeDoc = iframe.contentWindow.document; + let subIframe = iframeDoc.createElement("iframe"); + iframeDoc.body.appendChild(subIframe); + + listenOnce(subIframe, "load", function onload() { + subIframe.removeEventListener("load", onload, true); + + // And finally create a worker against this second iframe + let worker = Worker({ + window: subIframe.contentWindow, + contentScript: 'new ' + function WorkerScope() { + self.postMessage([ + window.top !== window, + frameElement, + window.parent !== window, + top.location.href, + parent.location.href, + ]); + }, + onMessage: function(msg) { + test.assert(msg[0], "window.top != window"); + test.assert(msg[1], "window.frameElement is defined"); + test.assert(msg[2], "window.parent != window"); + test.assertEqual(msg[3], contentWin.location.href, + "top.location refers to the toplevel content doc"); + test.assertEqual(msg[4], iframe.contentWindow.location.href, + "parent.location refers to the first iframe doc"); + done(); + } + }); + + }); + subIframe.setAttribute("src", "data:text/html;charset=utf-8,bar"); + + }); + iframe.setAttribute("src", "data:text/html;charset=utf-8,foo"); + } +); + +exports['test:check window attribute in toplevel documents'] = WorkerTest( + DEFAULT_CONTENT_URL, + function(test, browser, done) { + + let worker = Worker({ + window: browser.contentWindow, + contentScript: 'new ' + function WorkerScope() { + self.postMessage([ + window.top === window, + frameElement, + window.parent === window + ]); + }, + onMessage: function(msg) { + test.assert(msg[0], "window.top == window"); + test.assert(!msg[1], "window.frameElement is null"); + test.assert(msg[2], "window.parent == window"); + done(); + } + }); + } +); + +exports["test:check worker API with page history"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(test, browser, done) { + let url2 = "data:text/html;charset=utf-8,bar"; + + loadAndWait(browser, url2, function () { + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function WorkerScope() { + // Just before the content script is disable, we register a timeout + // that will be disable until the page gets visible again + self.on("pagehide", function () { + setTimeout(function () { + self.postMessage("timeout restored"); + }, 0); + }); + }, + contentScriptWhen: "start" + }); + + // postMessage works correctly when the page is visible + worker.postMessage("ok"); + + // We have to wait before going back into history, + // otherwise `goBack` won't do anything. + setTimeout(function () { + browser.goBack(); + }, 0); + + // Wait for the document to be hidden + browser.addEventListener("pagehide", function onpagehide() { + browser.removeEventListener("pagehide", onpagehide, false); + // Now any event sent to this worker should throw + test.assertRaises( + function () { worker.postMessage("data"); }, + "The page is currently hidden and can no longer be used until it" + + " is visible again.", + "postMessage should throw when the page is hidden in history" + ); + test.assertRaises( + function () { worker.port.emit("event"); }, + "The page is currently hidden and can no longer be used until it" + + " is visible again.", + "port.emit should throw when the page is hidden in history" + ); + + // Display the page with attached content script back in order to resume + // its timeout and receive the expected message. + // We have to delay this in order to not break the history. + // We delay for a non-zero amount of time in order to ensure that we + // do not receive the message immediatly, so that the timeout is + // actually disabled + setTimeout(function () { + worker.on("message", function (data) { + test.assert(data, "timeout restored"); + done(); + }); + browser.goForward(); + }, 500); + + }, false); + }); + + } +); diff --git a/tools/addon-sdk-1.12/test/test-context-menu.html b/tools/addon-sdk-1.12/test/test-context-menu.html new file mode 100644 index 0000000..b3f70a1 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-context-menu.html @@ -0,0 +1,46 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<html> + <head> + <meta charset="UTF-8"> + <title>Context menu test</title> + </head> + <body> + <p> + <img id="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="> + </p> + + <p> + <a id="link" href=""> + A simple link. + </a> + </p> + + <p> + <a href=""> + <span id="span-link"> + A span inside a link. + </span> + </a> + </p> + + <p id="text"> + Some text. + </p> + + <p> + <textarea id="textfield"> + A text field, + with some text. + </textarea> + </p> + + <p> + <iframe id="iframe" src="data:text/html;charset=utf-8,An iframe." + width="200" height="100"> + </iframe> + </p> + </body> +</html> diff --git a/tools/addon-sdk-1.12/test/test-context-menu.js b/tools/addon-sdk-1.12/test/test-context-menu.js new file mode 100644 index 0000000..727028f --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-context-menu.js @@ -0,0 +1,2093 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +let {Cc,Ci} = require("chrome"); +const { Loader } = require('sdk/test/loader'); +const timer = require("sdk/timers"); + +// These should match the same constants in the module. +const ITEM_CLASS = "jetpack-context-menu-item"; +const SEPARATOR_ID = "jetpack-context-menu-separator"; +const OVERFLOW_THRESH_DEFAULT = 10; +const OVERFLOW_THRESH_PREF = + "extensions.addon-sdk.context-menu.overflowThreshold"; +const OVERFLOW_MENU_ID = "jetpack-content-menu-overflow-menu"; +const OVERFLOW_POPUP_ID = "jetpack-content-menu-overflow-popup"; + +const TEST_DOC_URL = module.uri.replace(/\.js$/, ".html"); + +// Destroying items that were previously created should cause them to be absent +// from the menu. +exports.testConstructDestroy = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + // Create an item. + let item = new loader.cm.Item({ label: "item" }); + test.assertEqual(item.parentMenu, null, "item's parent menu should be null"); + + test.showMenu(null, function (popup) { + + // It should be present when the menu is shown. + test.checkMenu([item], [], []); + popup.hidePopup(); + + // Destroy the item. Multiple destroys should be harmless. + item.destroy(); + item.destroy(); + test.showMenu(null, function (popup) { + + // It should be removed from the menu. + test.checkMenu([], [], [item]); + test.done(); + }); + }); +}; + + +// Destroying an item twice should not cause an error. +exports.testDestroyTwice = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ label: "item" }); + item.destroy(); + item.destroy(); + + test.pass("Destroying an item twice should not cause an error."); + test.done(); +}; + + +// CSS selector contexts should cause their items to be present in the menu +// when the menu is invoked on nodes that match the selectors. +exports.testSelectorContextMatch = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ + label: "item", + data: "item", + context: loader.cm.SelectorContext("img") + }); + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("image"), function (popup) { + test.checkMenu([item], [], []); + test.done(); + }); + }); +}; + + +// CSS selector contexts should cause their items to be present in the menu +// when the menu is invoked on nodes that have ancestors that match the +// selectors. +exports.testSelectorAncestorContextMatch = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ + label: "item", + data: "item", + context: loader.cm.SelectorContext("a[href]") + }); + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("span-link"), function (popup) { + test.checkMenu([item], [], []); + test.done(); + }); + }); +}; + + +// CSS selector contexts should cause their items to be absent from the menu +// when the menu is not invoked on nodes that match or have ancestors that +// match the selectors. +exports.testSelectorContextNoMatch = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ + label: "item", + data: "item", + context: loader.cm.SelectorContext("img") + }); + + test.showMenu(null, function (popup) { + test.checkMenu([], [item], []); + test.done(); + }); +}; + + +// Page contexts should cause their items to be present in the menu when the +// menu is not invoked on an active element. +exports.testPageContextMatch = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let items = [ + new loader.cm.Item({ + label: "item 0" + }), + new loader.cm.Item({ + label: "item 1", + context: undefined + }), + new loader.cm.Item({ + label: "item 2", + context: loader.cm.PageContext() + }), + new loader.cm.Item({ + label: "item 3", + context: [loader.cm.PageContext()] + }) + ]; + + test.showMenu(null, function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); +}; + + +// Page contexts should cause their items to be absent from the menu when the +// menu is invoked on an active element. +exports.testPageContextNoMatch = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let items = [ + new loader.cm.Item({ + label: "item 0" + }), + new loader.cm.Item({ + label: "item 1", + context: undefined + }), + new loader.cm.Item({ + label: "item 2", + context: loader.cm.PageContext() + }), + new loader.cm.Item({ + label: "item 3", + context: [loader.cm.PageContext()] + }) + ]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("image"), function (popup) { + test.checkMenu([], items, []); + test.done(); + }); + }); +}; + + +// Selection contexts should cause items to appear when a selection exists. +exports.testSelectionContextMatch = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = loader.cm.Item({ + label: "item", + context: loader.cm.SelectionContext() + }); + + test.withTestDoc(function (window, doc) { + window.getSelection().selectAllChildren(doc.body); + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + test.done(); + }); + }); +}; + + +// Selection contexts should cause items to appear when a selection exists in +// a text field. +exports.testSelectionContextMatchInTextField = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = loader.cm.Item({ + label: "item", + context: loader.cm.SelectionContext() + }); + + test.withTestDoc(function (window, doc) { + let textfield = doc.getElementById("textfield"); + textfield.setSelectionRange(0, textfield.value.length); + test.showMenu(textfield, function (popup) { + test.checkMenu([item], [], []); + test.done(); + }); + }); +}; + + +// Selection contexts should not cause items to appear when a selection does +// not exist in a text field. +exports.testSelectionContextNoMatchInTextField = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = loader.cm.Item({ + label: "item", + context: loader.cm.SelectionContext() + }); + + test.withTestDoc(function (window, doc) { + let textfield = doc.getElementById("textfield"); + textfield.setSelectionRange(0, 0); + test.showMenu(textfield, function (popup) { + test.checkMenu([], [item], []); + test.done(); + }); + }); +}; + + +// Selection contexts should not cause items to appear when a selection does +// not exist. +exports.testSelectionContextNoMatch = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = loader.cm.Item({ + label: "item", + context: loader.cm.SelectionContext() + }); + + test.showMenu(null, function (popup) { + test.checkMenu([], [item], []); + test.done(); + }); +}; + + +// URL contexts should cause items to appear on pages that match. +exports.testURLContextMatch = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let items = [ + loader.cm.Item({ + label: "item 0", + context: loader.cm.URLContext(TEST_DOC_URL) + }), + loader.cm.Item({ + label: "item 1", + context: loader.cm.URLContext([TEST_DOC_URL, "*.bogus.com"]) + }) + ]; + + test.withTestDoc(function (window, doc) { + test.showMenu(null, function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + + +// URL contexts should not cause items to appear on pages that do not match. +exports.testURLContextNoMatch = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let items = [ + loader.cm.Item({ + label: "item 0", + context: loader.cm.URLContext("*.bogus.com") + }), + loader.cm.Item({ + label: "item 1", + context: loader.cm.URLContext(["*.bogus.com", "*.gnarly.com"]) + }) + ]; + + test.withTestDoc(function (window, doc) { + test.showMenu(null, function (popup) { + test.checkMenu([], items, []); + test.done(); + }); + }); +}; + + +// Removing a non-matching URL context after its item is created and the page is +// loaded should cause the item's content script to be evaluated. +exports.testURLContextRemove = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let shouldBeEvaled = false; + let context = loader.cm.URLContext("*.bogus.com"); + let item = loader.cm.Item({ + label: "item", + context: context, + contentScript: 'self.postMessage("ok");', + onMessage: function (msg) { + test.assert(shouldBeEvaled, + "content script should be evaluated when expected"); + shouldBeEvaled = false; + test.done(); + } + }); + + test.withTestDoc(function (window, doc) { + shouldBeEvaled = true; + item.context.remove(context); + }); +}; + + +// Adding a non-matching URL context after its item is created and the page is +// loaded should cause the item's worker to be destroyed. +exports.testURLContextAdd = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = loader.cm.Item({ label: "item" }); + + test.withTestDoc(function (window, doc) { + let privatePropsKey = loader.globalScope.PRIVATE_PROPS_KEY; + let workerReg = item.valueOf(privatePropsKey)._workerReg; + + let found = false; + for each (let winWorker in workerReg.winWorkers) { + if (winWorker.win === window) { + found = true; + break; + } + } + this.test.assert(found, "window should be present in worker registry"); + + item.context.add(loader.cm.URLContext("*.bogus.com")); + + for each (let winWorker in workerReg.winWorkers) + this.test.assertNotEqual(winWorker.win, window, + "window should not be present in worker registry"); + + test.done(); + }); +}; + + +// Content contexts that return true should cause their items to be present +// in the menu. +exports.testContentContextMatch = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ + label: "item", + contentScript: 'self.on("context", function () true);' + }); + + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + test.done(); + }); +}; + + +// Content contexts that return false should cause their items to be absent +// from the menu. +exports.testContentContextNoMatch = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ + label: "item", + contentScript: 'self.on("context", function () false);' + }); + + test.showMenu(null, function (popup) { + test.checkMenu([], [item], []); + test.done(); + }); +}; + + +// Content contexts that return a string should cause their items to be present +// in the menu and the items' labels to be updated. +exports.testContentContextMatchString = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ + label: "first label", + contentScript: 'self.on("context", function () "second label");' + }); + + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + test.assertEqual(item.label, "second label", + "item's label should be updated"); + test.done(); + }); +}; + + +// Ensure that contentScripFile is working correctly +exports.testContentScriptFile = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + // Reject remote files + test.assertRaises(function() { + new loader.cm.Item({ + label: "item", + contentScriptFile: "http://mozilla.com/context-menu.js" + }); + }, + "The 'contentScriptFile' option must be a local file URL " + + "or an array of local file URLs.", + "Item throws when contentScriptFile is a remote URL"); + + // But accept files from data folder + let item = new loader.cm.Item({ + label: "item", + contentScriptFile: require("sdk/self").data.url("test-context-menu.js") + }); + + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + test.done(); + }); +}; + + +// The args passed to context listeners should be correct. +exports.testContentContextArgs = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + let callbacks = 0; + + let item = new loader.cm.Item({ + label: "item", + contentScript: 'self.on("context", function (node) {' + + ' self.postMessage(node.tagName);' + + ' return false;' + + '});', + onMessage: function (tagName) { + test.assertEqual(tagName, "HTML", "node should be an HTML element"); + if (++callbacks == 2) test.done(); + } + }); + + test.showMenu(null, function () { + if (++callbacks == 2) test.done(); + }); +}; + +// Multiple contexts imply intersection, not union, and content context +// listeners should not be called if all declarative contexts are not current. +exports.testMultipleContexts = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ + label: "item", + context: [loader.cm.SelectorContext("a[href]"), loader.cm.PageContext()], + contentScript: 'self.on("context", function () self.postMessage());', + onMessage: function () { + test.fail("Context listener should not be called"); + } + }); + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("span-link"), function (popup) { + test.checkMenu([], [item], []); + test.done(); + }); + }); +}; + +// Once a context is removed, it should no longer cause its item to appear. +exports.testRemoveContext = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let ctxt = loader.cm.SelectorContext("img"); + let item = new loader.cm.Item({ + label: "item", + context: ctxt + }); + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("image"), function (popup) { + + // The item should be present at first. + test.checkMenu([item], [], []); + popup.hidePopup(); + + // Remove the img context and check again. + item.context.remove(ctxt); + test.showMenu(doc.getElementById("image"), function (popup) { + test.checkMenu([], [item], []); + test.done(); + }); + }); + }); +}; + + +// Lots of items should overflow into the overflow submenu. +exports.testOverflow = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let items = []; + for (let i = 0; i < OVERFLOW_THRESH_DEFAULT + 1; i++) { + let item = new loader.cm.Item({ label: "item " + i }); + items.push(item); + } + + test.showMenu(null, function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); +}; + + +// Module unload should cause all items to be removed. +exports.testUnload = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ label: "item" }); + + test.showMenu(null, function (popup) { + + // The menu should contain the item. + test.checkMenu([item], [], []); + popup.hidePopup(); + + // Unload the module. + loader.unload(); + test.showMenu(null, function (popup) { + + // The item should be removed from the menu. + test.checkMenu([], [], [item]); + test.done(); + }); + }); +}; + + +// Using multiple module instances to add items without causing overflow should +// work OK. Assumes OVERFLOW_THRESH_DEFAULT <= 2. +exports.testMultipleModulesAdd = function (test) { + test = new TestHelper(test); + let loader0 = test.newLoader(); + let loader1 = test.newLoader(); + + // Use each module to add an item, then unload each module in turn. + let item0 = new loader0.cm.Item({ label: "item 0" }); + let item1 = new loader1.cm.Item({ label: "item 1" }); + + test.showMenu(null, function (popup) { + + // The menu should contain both items. + test.checkMenu([item0, item1], [], []); + popup.hidePopup(); + + // Unload the first module. + loader0.unload(); + test.showMenu(null, function (popup) { + + // The first item should be removed from the menu. + test.checkMenu([item1], [], [item0]); + popup.hidePopup(); + + // Unload the second module. + loader1.unload(); + test.showMenu(null, function (popup) { + + // Both items should be removed from the menu. + test.checkMenu([], [], [item0, item1]); + test.done(); + }); + }); + }); +}; + + +// Using multiple module instances to add items causing overflow should work OK. +exports.testMultipleModulesAddOverflow = function (test) { + test = new TestHelper(test); + let loader0 = test.newLoader(); + let loader1 = test.newLoader(); + + // Use module 0 to add OVERFLOW_THRESH_DEFAULT items. + let items0 = []; + for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) { + let item = new loader0.cm.Item({ label: "item 0 " + i }); + items0.push(item); + } + + // Use module 1 to add one item. + let item1 = new loader1.cm.Item({ label: "item 1" }); + + let allItems = items0.concat(item1); + + test.showMenu(null, function (popup) { + + // The menu should contain all items in overflow. + test.checkMenu(allItems, [], []); + popup.hidePopup(); + + // Unload the first module. + loader0.unload(); + test.showMenu(null, function (popup) { + + // The first items should be removed from the menu, which should not + // overflow. + test.checkMenu([item1], [], items0); + popup.hidePopup(); + + // Unload the second module. + loader1.unload(); + test.showMenu(null, function (popup) { + + // All items should be removed from the menu. + test.checkMenu([], [], allItems); + test.done(); + }); + }); + }); +}; + + +// Using multiple module instances to modify the menu without causing overflow +// should work OK. This test creates two loaders and: +// loader0 create item -> loader1 create item -> loader0.unload -> +// loader1.unload +exports.testMultipleModulesDiffContexts1 = function (test) { + test = new TestHelper(test); + let loader0 = test.newLoader(); + let loader1 = test.newLoader(); + + let item0 = new loader0.cm.Item({ + label: "item 0", + context: loader0.cm.SelectorContext("img") + }); + + let item1 = new loader1.cm.Item({ label: "item 1" }); + + test.showMenu(null, function (popup) { + + // The menu should contain item1. + test.checkMenu([item1], [item0], []); + popup.hidePopup(); + + // Unload module 0. + loader0.unload(); + test.showMenu(null, function (popup) { + + // item0 should be removed from the menu. + test.checkMenu([item1], [], [item0]); + popup.hidePopup(); + + // Unload module 1. + loader1.unload(); + test.showMenu(null, function (popup) { + + // Both items should be removed from the menu. + test.checkMenu([], [], [item0, item1]); + test.done(); + }); + }); + }); +}; + + +// Using multiple module instances to modify the menu without causing overflow +// should work OK. This test creates two loaders and: +// loader1 create item -> loader0 create item -> loader0.unload -> +// loader1.unload +exports.testMultipleModulesDiffContexts2 = function (test) { + test = new TestHelper(test); + let loader0 = test.newLoader(); + let loader1 = test.newLoader(); + + let item1 = new loader1.cm.Item({ label: "item 1" }); + + let item0 = new loader0.cm.Item({ + label: "item 0", + context: loader0.cm.SelectorContext("img") + }); + + test.showMenu(null, function (popup) { + + // The menu should contain item1. + test.checkMenu([item1], [item0], []); + popup.hidePopup(); + + // Unload module 0. + loader0.unload(); + test.showMenu(null, function (popup) { + + // item0 should be removed from the menu. + test.checkMenu([item1], [], [item0]); + popup.hidePopup(); + + // Unload module 1. + loader1.unload(); + test.showMenu(null, function (popup) { + + // Both items should be removed from the menu. + test.checkMenu([], [], [item0, item1]); + test.done(); + }); + }); + }); +}; + + +// Using multiple module instances to modify the menu without causing overflow +// should work OK. This test creates two loaders and: +// loader0 create item -> loader1 create item -> loader1.unload -> +// loader0.unload +exports.testMultipleModulesDiffContexts3 = function (test) { + test = new TestHelper(test); + let loader0 = test.newLoader(); + let loader1 = test.newLoader(); + + let item0 = new loader0.cm.Item({ + label: "item 0", + context: loader0.cm.SelectorContext("img") + }); + + let item1 = new loader1.cm.Item({ label: "item 1" }); + + test.showMenu(null, function (popup) { + + // The menu should contain item1. + test.checkMenu([item1], [item0], []); + popup.hidePopup(); + + // Unload module 1. + loader1.unload(); + test.showMenu(null, function (popup) { + + // item1 should be removed from the menu. + test.checkMenu([], [item0], [item1]); + popup.hidePopup(); + + // Unload module 0. + loader0.unload(); + test.showMenu(null, function (popup) { + + // Both items should be removed from the menu. + test.checkMenu([], [], [item0, item1]); + test.done(); + }); + }); + }); +}; + + +// Using multiple module instances to modify the menu without causing overflow +// should work OK. This test creates two loaders and: +// loader1 create item -> loader0 create item -> loader1.unload -> +// loader0.unload +exports.testMultipleModulesDiffContexts4 = function (test) { + test = new TestHelper(test); + let loader0 = test.newLoader(); + let loader1 = test.newLoader(); + + let item1 = new loader1.cm.Item({ label: "item 1" }); + + let item0 = new loader0.cm.Item({ + label: "item 0", + context: loader0.cm.SelectorContext("img") + }); + + test.showMenu(null, function (popup) { + + // The menu should contain item1. + test.checkMenu([item1], [item0], []); + popup.hidePopup(); + + // Unload module 1. + loader1.unload(); + test.showMenu(null, function (popup) { + + // item1 should be removed from the menu. + test.checkMenu([], [item0], [item1]); + popup.hidePopup(); + + // Unload module 0. + loader0.unload(); + test.showMenu(null, function (popup) { + + // Both items should be removed from the menu. + test.checkMenu([], [], [item0, item1]); + test.done(); + }); + }); + }); +}; + + +// Test interactions between a loaded module, unloading another module, and the +// menu separator and overflow submenu. +exports.testMultipleModulesAddRemove = function (test) { + test = new TestHelper(test); + let loader0 = test.newLoader(); + let loader1 = test.newLoader(); + + let item = new loader0.cm.Item({ label: "item" }); + + test.showMenu(null, function (popup) { + + // The menu should contain the item. + test.checkMenu([item], [], []); + popup.hidePopup(); + + // Remove the item. + item.destroy(); + test.showMenu(null, function (popup) { + + // The item should be removed from the menu. + test.checkMenu([], [], [item]); + popup.hidePopup(); + + // Unload module 1. + loader1.unload(); + test.showMenu(null, function (popup) { + + // There shouldn't be any errors involving the menu separator or + // overflow submenu. + test.checkMenu([], [], [item]); + test.done(); + }); + }); + }); +}; + + +// An item's click listener should work. +exports.testItemClick = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ + label: "item", + data: "item data", + contentScript: 'self.on("click", function (node, data) {' + + ' let Ci = Components["interfaces"];' + + ' self.postMessage({' + + ' tagName: node.tagName,' + + ' data: data' + + ' });' + + '});', + onMessage: function (data) { + test.assertEqual(this, item, "`this` inside onMessage should be item"); + test.assertEqual(data.tagName, "HTML", "node should be an HTML element"); + test.assertEqual(data.data, item.data, "data should be item data"); + test.done(); + } + }); + + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + let elt = test.getItemElt(popup, item); + elt.click(); + }); +}; + + +// A menu's click listener should work and receive bubbling clicks from +// sub-items appropriately. This also tests menus and ensures that when a CSS +// selector context matches the clicked node's ancestor, the matching ancestor +// is passed to listeners as the clicked node. +exports.testMenuClick = function (test) { + // Create a top-level menu, submenu, and item, like this: + // topMenu -> submenu -> item + // Click the item and make sure the click bubbles. + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ + label: "submenu item", + data: "submenu item data" + }); + + let submenu = new loader.cm.Menu({ + label: "submenu", + items: [item] + }); + + let topMenu = new loader.cm.Menu({ + label: "top menu", + contentScript: 'self.on("click", function (node, data) {' + + ' let Ci = Components["interfaces"];' + + ' self.postMessage({' + + ' tagName: node.tagName,' + + ' data: data' + + ' });' + + '});', + onMessage: function (data) { + test.assertEqual(this, topMenu, "`this` inside top menu should be menu"); + test.assertEqual(data.tagName, "A", "Clicked node should be anchor"); + test.assertEqual(data.data, item.data, + "Clicked item data should be correct"); + test.done(); + }, + items: [submenu], + context: loader.cm.SelectorContext("a") + }); + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("span-link"), function (popup) { + test.checkMenu([topMenu], [], []); + let topMenuElt = test.getItemElt(popup, topMenu); + let topMenuPopup = topMenuElt.firstChild; + let submenuElt = test.getItemElt(topMenuPopup, submenu); + let submenuPopup = submenuElt.firstChild; + let itemElt = test.getItemElt(submenuPopup, item); + itemElt.click(); + }); + }); +}; + +// Click listeners should work when multiple modules are loaded. +exports.testItemClickMultipleModules = function (test) { + test = new TestHelper(test); + let loader0 = test.newLoader(); + let loader1 = test.newLoader(); + + let item0 = loader0.cm.Item({ + label: "loader 0 item", + contentScript: 'self.on("click", self.postMessage);', + onMessage: function () { + test.fail("loader 0 item should not emit click event"); + } + }); + let item1 = loader1.cm.Item({ + label: "loader 1 item", + contentScript: 'self.on("click", self.postMessage);', + onMessage: function () { + test.pass("loader 1 item clicked as expected"); + test.done(); + } + }); + + test.showMenu(null, function (popup) { + test.checkMenu([item0, item1], [], []); + let item1Elt = test.getItemElt(popup, item1); + item1Elt.click(); + }); +}; + + +// Adding a separator to a submenu should work OK. +exports.testSeparator = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let menu = new loader.cm.Menu({ + label: "submenu", + items: [new loader.cm.Separator()] + }); + + test.showMenu(null, function (popup) { + test.checkMenu([menu], [], []); + test.done(); + }); +}; + + +// Existing context menu modifications should apply to new windows. +exports.testNewWindow = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ label: "item" }); + + test.withNewWindow(function () { + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + test.done(); + }); + }); +}; + + +// When a new window is opened, items added by an unloaded module should not +// be present in the menu. +exports.testNewWindowMultipleModules = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + let item = new loader.cm.Item({ label: "item" }); + + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + popup.hidePopup(); + loader.unload(); + test.withNewWindow(function () { + test.showMenu(null, function (popup) { + test.checkMenu([], [], []); + test.done(); + }); + }); + }); +}; + + +// Items in the context menu should be sorted according to locale. +exports.testSorting = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + // Make an unsorted items list. It'll look like this: + // item 1, item 0, item 3, item 2, item 5, item 4, ... + let items = []; + for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i += 2) { + items.push(new loader.cm.Item({ label: "item " + (i + 1) })); + items.push(new loader.cm.Item({ label: "item " + i })); + } + + test.showMenu(null, function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); +}; + + +// Items in the overflow menu should be sorted according to locale. +exports.testSortingOverflow = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + // Make an unsorted items list. It'll look like this: + // item 1, item 0, item 3, item 2, item 5, item 4, ... + let items = []; + for (let i = 0; i < OVERFLOW_THRESH_DEFAULT * 2; i += 2) { + items.push(new loader.cm.Item({ label: "item " + (i + 1) })); + items.push(new loader.cm.Item({ label: "item " + i })); + } + + test.showMenu(null, function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); +}; + + +// Multiple modules shouldn't interfere with sorting. +exports.testSortingMultipleModules = function (test) { + test = new TestHelper(test); + let loader0 = test.newLoader(); + let loader1 = test.newLoader(); + + let items0 = []; + let items1 = []; + for (let i = 0; i < OVERFLOW_THRESH_DEFAULT; i++) { + if (i % 2) { + let item = new loader0.cm.Item({ label: "item " + i }); + items0.push(item); + } + else { + let item = new loader1.cm.Item({ label: "item " + i }); + items1.push(item); + } + } + let allItems = items0.concat(items1); + + test.showMenu(null, function (popup) { + + // All items should be present and sorted. + test.checkMenu(allItems, [], []); + popup.hidePopup(); + loader0.unload(); + loader1.unload(); + test.showMenu(null, function (popup) { + + // All items should be removed. + test.checkMenu([], [], allItems); + test.done(); + }); + }); +}; + + +// The binary search of insertionPoint should work OK. +exports.testInsertionPoint = function (test) { + function mockElts(labels) { + return labels.map(function (label) { + return { label: label, getAttribute: function (l) label }; + }); + } + + test = new TestHelper(test); + let loader = test.newLoader(); + let insertionPoint = loader.globalScope.insertionPoint; + + let ip = insertionPoint("a", []); + test.assertStrictEqual(ip, null, "Insertion point should be null"); + + ip = insertionPoint("a", mockElts(["b"])); + test.assertEqual(ip.label, "b", "Insertion point should be 'b'"); + + ip = insertionPoint("c", mockElts(["b"])); + test.assertStrictEqual(ip, null, "Insertion point should be null"); + + ip = insertionPoint("b", mockElts(["a", "c"])); + test.assertEqual(ip.label, "c", "Insertion point should be 'c'"); + + ip = insertionPoint("c", mockElts(["a", "b", "d"])); + test.assertEqual(ip.label, "d", "Insertion point should be 'd'"); + + ip = insertionPoint("a", mockElts(["b", "c", "d"])); + test.assertEqual(ip.label, "b", "Insertion point should be 'b'"); + + ip = insertionPoint("d", mockElts(["a", "b", "c"])); + test.assertStrictEqual(ip, null, "Insertion point should be null"); + + test.done(); +}; + + +// Content click handlers and context handlers should be able to communicate, +// i.e., they're eval'ed in the same worker and sandbox. +exports.testContentCommunication = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = new loader.cm.Item({ + label: "item", + contentScript: 'var potato;' + + 'self.on("context", function () {' + + ' potato = "potato";' + + ' return true;' + + '});' + + 'self.on("click", function () {' + + ' self.postMessage(potato);' + + '});', + }); + + item.on("message", function (data) { + test.assertEqual(data, "potato", "That's a lot of potatoes!"); + test.done(); + }); + + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + let elt = test.getItemElt(popup, item); + elt.click(); + }); +}; + + +// When the context menu is invoked on a tab that was already open when the +// module was loaded, it should contain the expected items and content workers +// should function as expected. +exports.testLoadWithOpenTab = function (test) { + test = new TestHelper(test); + test.withTestDoc(function (window, doc) { + let loader = test.newLoader(); + let item = new loader.cm.Item({ + label: "item", + contentScript: + 'self.on("click", function () self.postMessage("click"));', + onMessage: function (msg) { + if (msg === "click") + test.done(); + } + }); + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + test.getItemElt(popup, item).click(); + }); + }); +}; + +// Bug 732716: Ensure that the node given in `click` event works fine +// (i.e. is correctly wrapped) +exports.testDrawImageOnClickNode = function (test) { + test = new TestHelper(test); + test.withTestDoc(function (window, doc) { + let loader = test.newLoader(); + let item = new loader.cm.Item({ + label: "item", + context: loader.cm.SelectorContext("img"), + contentScript: "new " + function() { + self.on("click", function (img, data) { + let ctx = document.createElement("canvas").getContext("2d"); + ctx.drawImage(img, 1, 1, 1, 1); + self.postMessage("done"); + }); + }, + onMessage: function (msg) { + if (msg === "done") + test.done(); + } + }); + test.showMenu(doc.getElementById("image"), function (popup) { + test.checkMenu([item], [], []); + test.getItemElt(popup, item).click(); + }); + }); +}; + +// Setting an item's label before the menu is ever shown should correctly change +// its label and, if necessary, its order within the menu. +exports.testSetLabelBeforeShow = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let items = [ + new loader.cm.Item({ label: "a" }), + new loader.cm.Item({ label: "b" }) + ] + items[0].label = "z"; + test.assertEqual(items[0].label, "z"); + + test.showMenu(null, function (popup) { + test.checkMenu([items[1], items[0]], [], []); + test.done(); + }); +}; + + +// Setting an item's label after the menu is shown should correctly change its +// label and, if necessary, its order within the menu. +exports.testSetLabelAfterShow = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let items = [ + new loader.cm.Item({ label: "a" }), + new loader.cm.Item({ label: "b" }) + ]; + + test.showMenu(null, function (popup) { + test.checkMenu(items, [], []); + popup.hidePopup(); + + items[0].label = "z"; + test.assertEqual(items[0].label, "z"); + test.showMenu(null, function (popup) { + test.checkMenu([items[1], items[0]], [], []); + test.done(); + }); + }); +}; + + +// Setting an item's label before the menu is ever shown should correctly change +// its label and, if necessary, its order within the menu if the item is in the +// overflow submenu. +exports.testSetLabelBeforeShowOverflow = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let prefs = loader.loader.require("sdk/preferences/service"); + prefs.set(OVERFLOW_THRESH_PREF, 0); + + let items = [ + new loader.cm.Item({ label: "a" }), + new loader.cm.Item({ label: "b" }) + ] + items[0].label = "z"; + test.assertEqual(items[0].label, "z"); + + test.showMenu(null, function (popup) { + test.checkMenu([items[1], items[0]], [], []); + prefs.set(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT); + test.done(); + }); +}; + + +// Setting an item's label after the menu is shown should correctly change its +// label and, if necessary, its order within the menu if the item is in the +// overflow submenu. +exports.testSetLabelAfterShowOverflow = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let prefs = loader.loader.require("sdk/preferences/service"); + prefs.set(OVERFLOW_THRESH_PREF, 0); + + let items = [ + new loader.cm.Item({ label: "a" }), + new loader.cm.Item({ label: "b" }) + ]; + + test.showMenu(null, function (popup) { + test.checkMenu(items, [], []); + popup.hidePopup(); + + items[0].label = "z"; + test.assertEqual(items[0].label, "z"); + test.showMenu(null, function (popup) { + test.checkMenu([items[1], items[0]], [], []); + prefs.set(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT); + test.done(); + }); + }); +}; + + +// Setting the label of an item in a Menu should work. +exports.testSetLabelMenuItem = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let menu = loader.cm.Menu({ + label: "menu", + items: [loader.cm.Item({ label: "a" })] + }); + menu.items[0].label = "z"; + + test.assertEqual(menu.items[0].label, "z"); + + test.showMenu(null, function (popup) { + test.checkMenu([menu], [], []); + test.done(); + }); +}; + + +// Menu.addItem() should work. +exports.testMenuAddItem = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let menu = loader.cm.Menu({ + label: "menu", + items: [ + loader.cm.Item({ label: "item 0" }) + ] + }); + menu.addItem(loader.cm.Item({ label: "item 1" })); + menu.addItem(loader.cm.Item({ label: "item 2" })); + + test.assertEqual(menu.items.length, 3, + "menu should have correct number of items"); + for (let i = 0; i < 3; i++) { + test.assertEqual(menu.items[i].label, "item " + i, + "item label should be correct"); + test.assertEqual(menu.items[i].parentMenu, menu, + "item's parent menu should be correct"); + } + + test.showMenu(null, function (popup) { + test.checkMenu([menu], [], []); + test.done(); + }); +}; + + +// Adding the same item twice to a menu should work as expected. +exports.testMenuAddItemTwice = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let menu = loader.cm.Menu({ + label: "menu", + items: [] + }); + let subitem = loader.cm.Item({ label: "item 1" }) + menu.addItem(subitem); + menu.addItem(loader.cm.Item({ label: "item 0" })); + menu.addItem(subitem); + + test.assertEqual(menu.items.length, 2, + "menu should have correct number of items"); + for (let i = 0; i < 2; i++) { + test.assertEqual(menu.items[i].label, "item " + i, + "item label should be correct"); + } + + test.showMenu(null, function (popup) { + test.checkMenu([menu], [], []); + test.done(); + }); +}; + + +// Menu.removeItem() should work. +exports.testMenuRemoveItem = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let subitem = loader.cm.Item({ label: "item 1" }); + let menu = loader.cm.Menu({ + label: "menu", + items: [ + loader.cm.Item({ label: "item 0" }), + subitem, + loader.cm.Item({ label: "item 2" }) + ] + }); + + // Removing twice should be harmless. + menu.removeItem(subitem); + menu.removeItem(subitem); + + test.assertEqual(subitem.parentMenu, null, + "item's parent menu should be correct"); + + test.assertEqual(menu.items.length, 2, + "menu should have correct number of items"); + test.assertEqual(menu.items[0].label, "item 0", + "item label should be correct"); + test.assertEqual(menu.items[1].label, "item 2", + "item label should be correct"); + + test.showMenu(null, function (popup) { + test.checkMenu([menu], [], []); + test.done(); + }); +}; + + +// Adding an item currently contained in one menu to another menu should work. +exports.testMenuItemSwap = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let subitem = loader.cm.Item({ label: "item" }); + let menu0 = loader.cm.Menu({ + label: "menu 0", + items: [subitem] + }); + let menu1 = loader.cm.Menu({ + label: "menu 1", + items: [] + }); + menu1.addItem(subitem); + + test.assertEqual(menu0.items.length, 0, + "menu should have correct number of items"); + + test.assertEqual(menu1.items.length, 1, + "menu should have correct number of items"); + test.assertEqual(menu1.items[0].label, "item", + "item label should be correct"); + + test.assertEqual(subitem.parentMenu, menu1, + "item's parent menu should be correct"); + + test.showMenu(null, function (popup) { + test.checkMenu([menu0, menu1], [], []); + test.done(); + }); +}; + + +// Destroying an item should remove it from its parent menu. +exports.testMenuItemDestroy = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let subitem = loader.cm.Item({ label: "item" }); + let menu = loader.cm.Menu({ + label: "menu", + items: [subitem] + }); + subitem.destroy(); + + test.assertEqual(menu.items.length, 0, + "menu should have correct number of items"); + test.assertEqual(subitem.parentMenu, null, + "item's parent menu should be correct"); + + test.showMenu(null, function (popup) { + test.checkMenu([menu], [], []); + test.done(); + }); +}; + + +// Setting Menu.items should work. +exports.testMenuItemsSetter = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let menu = loader.cm.Menu({ + label: "menu", + items: [ + loader.cm.Item({ label: "old item 0" }), + loader.cm.Item({ label: "old item 1" }) + ] + }); + menu.items = [ + loader.cm.Item({ label: "new item 0" }), + loader.cm.Item({ label: "new item 1" }), + loader.cm.Item({ label: "new item 2" }) + ]; + + test.assertEqual(menu.items.length, 3, + "menu should have correct number of items"); + for (let i = 0; i < 3; i++) { + test.assertEqual(menu.items[i].label, "new item " + i, + "item label should be correct"); + test.assertEqual(menu.items[i].parentMenu, menu, + "item's parent menu should be correct"); + } + + test.showMenu(null, function (popup) { + test.checkMenu([menu], [], []); + test.done(); + }); +}; + + +// Setting Item.data should work. +exports.testItemDataSetter = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let item = loader.cm.Item({ label: "old item 0", data: "old" }); + item.data = "new"; + + test.assertEqual(item.data, "new", "item should have correct data"); + + test.showMenu(null, function (popup) { + test.checkMenu([item], [], []); + test.done(); + }); +}; + + +// Open the test doc, load the module, make sure items appear when context- +// clicking the iframe. +exports.testAlreadyOpenIframe = function (test) { + test = new TestHelper(test); + test.withTestDoc(function (window, doc) { + let loader = test.newLoader(); + let item = new loader.cm.Item({ + label: "item" + }); + test.showMenu(doc.getElementById("iframe"), function (popup) { + test.checkMenu([item], [], []); + test.done(); + }); + }); +}; + + +// Test image support. +exports.testItemImage = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let imageURL = require("sdk/self").data.url("moz_favicon.ico"); + let item = new loader.cm.Item({ label: "item", image: imageURL }); + let menu = new loader.cm.Menu({ label: "menu", image: imageURL, items: [] }); + + test.showMenu(null, function (popup) { + test.checkMenu([item, menu], [], []); + + let imageURL2 = require("sdk/self").data.url("dummy.ico"); + item.image = imageURL2; + menu.image = imageURL2; + test.checkMenu([item, menu], [], []); + + item.image = null; + menu.image = null; + test.checkMenu([item, menu], [], []); + + test.done(); + }); +}; + + +// Menu.destroy should destroy the item tree rooted at that menu. +exports.testMenuDestroy = function (test) { + test = new TestHelper(test); + let loader = test.newLoader(); + + let menu = loader.cm.Menu({ + label: "menu", + items: [ + loader.cm.Item({ label: "item 0" }), + loader.cm.Menu({ + label: "item 1", + items: [ + loader.cm.Item({ label: "subitem 0" }), + loader.cm.Item({ label: "subitem 1" }), + loader.cm.Item({ label: "subitem 2" }) + ] + }), + loader.cm.Item({ label: "item 2" }) + ] + }); + menu.destroy(); + + let numRegistryEntries = 0; + loader.globalScope.browserManager.browserWins.forEach(function (bwin) { + for (let itemID in bwin.items) + numRegistryEntries++; + }); + test.assertEqual(numRegistryEntries, 0, "All items should be unregistered."); + + test.showMenu(null, function (popup) { + test.checkMenu([], [], [menu]); + test.done(); + }); +}; + + +// NO TESTS BELOW THIS LINE! /////////////////////////////////////////////////// + +// Run only a dummy test if context-menu doesn't support the host app. +if (!require("sdk/system/xul-app").is("Firefox")) { + module.exports = { + testAppNotSupported: function (test) { + test.pass("context-menu does not support this application."); + } + }; +} + + +// This makes it easier to run tests by handling things like opening the menu, +// opening new windows, making assertions, etc. Methods on |test| can be called +// on instances of this class. Don't forget to call done() to end the test! +// WARNING: This looks up items in popups by comparing labels, so don't give two +// items the same label. +function TestHelper(test) { + // default waitUntilDone timeout is 10s, which is too short on the win7 + // buildslave + test.waitUntilDone(30*1000); + this.test = test; + this.loaders = []; + this.browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator). + getMostRecentWindow("navigator:browser"); +} + +TestHelper.prototype = { + get contextMenuPopup() { + return this.browserWindow.document.getElementById("contentAreaContextMenu"); + }, + + get contextMenuSeparator() { + return this.browserWindow.document.getElementById(SEPARATOR_ID); + }, + + get overflowPopup() { + return this.browserWindow.document.getElementById(OVERFLOW_POPUP_ID); + }, + + get overflowSubmenu() { + return this.browserWindow.document.getElementById(OVERFLOW_MENU_ID); + }, + + get tabBrowser() { + return this.browserWindow.gBrowser; + }, + + // Methods on the wrapped test can be called on this object. + __noSuchMethod__: function (methodName, args) { + this.test[methodName].apply(this.test, args); + }, + + // Asserts that absentItems -- an array of items that should not match the + // current context -- aren't present in the menu. + checkAbsentItems: function (presentItems, absentItems) { + for (let i = 0; i < absentItems.length; i++) { + let item = absentItems[i]; + let elt = this.getItemElt(this.contextMenuPopup, item); + + // The implementation actually hides items rather than removing or not + // adding them in the first place, but that's an implementation detail. + this.test.assert(!elt || elt.hidden, + "Item should not be present in top-level menu"); + + if (this.shouldOverflow(presentItems)) { + elt = getItemElt(this.overflowPopup, item); + this.test.assert(!elt || elt.hidden, + "Item should not be present in overflow submenu"); + } + } + }, + + // Asserts that elt, a DOM element representing item, looks OK. + checkItemElt: function (elt, item) { + let itemType = this.getItemType(item); + + switch (itemType) { + case "Item": + this.test.assertEqual(elt.localName, "menuitem", + "Item DOM element should be a xul:menuitem"); + if (typeof(item.data) === "string") { + this.test.assertEqual(elt.getAttribute("value"), item.data, + "Item should have correct data"); + } + break + case "Menu": + this.test.assertEqual(elt.localName, "menu", + "Menu DOM element should be a xul:menu"); + let subPopup = elt.firstChild; + this.test.assert(subPopup, "xul:menu should have a child"); + this.test.assertEqual(subPopup.localName, "menupopup", + "xul:menu's first child should be a menupopup"); + break; + case "Separator": + this.test.assertEqual(elt.localName, "menuseparator", + "Separator DOM element should be a xul:menuseparator"); + break; + } + + if (itemType === "Item" || itemType === "Menu") { + this.test.assertEqual(elt.getAttribute("label"), item.label, + "Item should have correct title"); + if (typeof(item.image) === "string") + this.test.assertEqual(elt.getAttribute("image"), item.image, + "Item should have correct image"); + else + this.test.assert(!elt.hasAttribute("image"), + "Item should not have image"); + } + }, + + // Asserts that the context menu looks OK given the arguments. presentItems + // are items that should match the current context. absentItems are items + // that shouldn't. removedItems are items that have been removed from the + // menu. + checkMenu: function (presentItems, absentItems, removedItems) { + this.checkSeparator(presentItems); + this.checkOverflow(presentItems); + this.checkPresentItems(presentItems); + this.checkAbsentItems(presentItems, absentItems); + this.checkRemovedItems(removedItems); + this.checkSort(presentItems); + }, + + // Asserts that the overflow submenu is present or absent as appropriate for + // presentItems. + checkOverflow: function (presentItems) { + let submenu = this.overflowSubmenu; + if (this.shouldOverflow(presentItems)) { + this.test.assert(submenu && !submenu.hidden, + "Overflow submenu should be present"); + this.test.assert(submenu.localName, "menu", + "Overflow submenu should be a <menu>"); + let overflowPopup = this.overflowPopup; + this.test.assert(overflowPopup, + "Overflow submenu popup should be present"); + this.test.assert(overflowPopup.localName, "menupopup", + "Overflow submenu popup should be a <menupopup>"); + } + else { + this.test.assert(!submenu || submenu.hidden, + "Overflow submenu should be absent"); + } + }, + + // Asserts that the items that are present in the menu because they match the + // current context look OK. + checkPresentItems: function (presentItems) { + function recurse(popup, items, isTopLevel) { + items.forEach(function (item) { + let elt = this.getItemElt(popup, item); + + if (isTopLevel) { + if (this.shouldOverflow(items)) { + this.test.assert(!elt || elt.hidden, + "Item should not be present in top-level menu"); + + let overflowPopup = this.overflowPopup; + this.test.assert(overflowPopup, + "Overflow submenu should be present"); + + elt = this.getItemElt(overflowPopup, item); + this.test.assert(elt && !elt.hidden, + "Item should be present in overflow submenu"); + } + else { + this.test.assert(elt && !elt.hidden, + "Item should be present in top-level menu"); + } + } + else { + this.test.assert(elt && !elt.hidden, + "Item should be present in menu"); + } + + this.checkItemElt(elt, item); + if (this.getItemType(item) === "Menu") + recurse.call(this, elt.firstChild, item.items, false); + }, this); + } + + recurse.call(this, this.contextMenuPopup, presentItems, true); + }, + + // Asserts that items that have been removed from the menu are really removed. + checkRemovedItems: function (removedItems) { + for (let i = 0; i < removedItems.length; i++) { + let item = removedItems[i]; + + let elt = this.getItemElt(this.contextMenuPopup, item); + this.test.assert(!elt, "Item should be removed from top-level menu"); + + let overflowPopup = this.overflowPopup; + if (overflowPopup) { + elt = this.getItemElt(overflowPopup, item); + this.test.assert(!elt, "Item should be removed from overflow submenu"); + } + } + }, + + // Asserts that the menu separator separating standard items from our items + // looks OK. + checkSeparator: function (presentItems) { + let sep = this.contextMenuSeparator; + if (presentItems.length) { + this.test.assert(sep && !sep.hidden, "Menu separator should be present"); + this.test.assertEqual(sep.localName, "menuseparator", + "Menu separator should be a <menuseparator>"); + } + else { + this.test.assert(!sep || sep.hidden, "Menu separator should be absent"); + } + }, + + // Asserts that our items are sorted. + checkSort: function (presentItems) { + // Get the first item in sorted order, get its elt, walk the nextSibling + // chain, making sure each is greater than the previous. + if (presentItems.length) { + let sorted = presentItems.slice(0). + sort(function (a, b) a.label.localeCompare(b.label)); + let elt = this.shouldOverflow(presentItems) ? + this.getItemElt(this.overflowPopup, sorted[0]) : + this.getItemElt(this.contextMenuPopup, sorted[0]); + let numElts = 1; + while (elt.nextSibling && + elt.nextSibling.className.split(/\s+/).indexOf(ITEM_CLASS) >= 0) { + let eltLabel = elt.getAttribute("label"); + let nextLabel = elt.nextSibling.getAttribute("label"); + this.test.assert(eltLabel.localeCompare(nextLabel) < 0, + "Item label should be < next item's label"); + elt = elt.nextSibling; + numElts++; + } + this.test.assertEqual(numElts, presentItems.length, + "The first item in sorted order should have the " + + "first element in sorted order"); + } + }, + + // Attaches an event listener to node. The listener is automatically removed + // when it's fired (so it's assumed it will fire), and callback is called + // after a short delay. Since the module we're testing relies on the same + // event listeners to do its work, this is to give them a little breathing + // room before callback runs. Inside callback |this| is this object. + delayedEventListener: function (node, event, callback, useCapture) { + const self = this; + node.addEventListener(event, function handler(evt) { + node.removeEventListener(event, handler, useCapture); + timer.setTimeout(function () { + try { + callback.call(self, evt); + } + catch (err) { + self.test.exception(err); + self.test.done(); + } + }, 20); + }, useCapture); + }, + + // Call to finish the test. + done: function () { + function commonDone() { + if (this.tab) { + this.tabBrowser.removeTab(this.tab); + this.tabBrowser.selectedTab = this.oldSelectedTab; + } + while (this.loaders.length) { + let browserManager = this.loaders[0].globalScope.browserManager; + let topLevelItems = browserManager.topLevelItems.slice(); + let privatePropsKey = this.loaders[0].globalScope.PRIVATE_PROPS_KEY; + let workerRegs = topLevelItems.map(function (item) { + return item.valueOf(privatePropsKey)._workerReg; + }); + + this.loaders[0].unload(); + + // Make sure the browser manager is cleaned up. + this.test.assertEqual(browserManager.browserWins.length, 0, + "browserManager should have no windows left"); + this.test.assertEqual(browserManager.topLevelItems.length, 0, + "browserManager should have no items left"); + this.test.assert(!("contentWins" in browserManager), + "browserManager should have no content windows left"); + + // Make sure the items' worker registries are cleaned up. + topLevelItems.forEach(function (item) { + this.test.assert(!("_workerReg" in item.valueOf(privatePropsKey)), + "item's worker registry should be removed"); + }, this); + workerRegs.forEach(function (workerReg) { + this.test.assertEqual(Object.keys(workerReg.winWorkers).length, 0, + "worker registry should be empty"); + this.test.assertEqual( + Object.keys(workerReg.winsWithoutWorkers).length, 0, + "worker registry list of windows without workers should be empty"); + }, this); + } + this.test.done(); + } + + function closeBrowserWindow() { + if (this.oldBrowserWindow) { + this.delayedEventListener(this.browserWindow, "unload", commonDone, + false); + this.browserWindow.close(); + this.browserWindow = this.oldBrowserWindow; + delete this.oldBrowserWindow; + } + else { + commonDone.call(this); + } + }; + + if (this.contextMenuPopup.state == "closed") { + closeBrowserWindow.call(this); + } + else { + this.delayedEventListener(this.contextMenuPopup, "popuphidden", + function () closeBrowserWindow.call(this), + false); + this.contextMenuPopup.hidePopup(); + } + }, + + // Returns the DOM element in popup corresponding to item. + // WARNING: The element is found by comparing labels, so don't give two items + // the same label. + getItemElt: function (popup, item) { + let nodes = popup.childNodes; + for (let i = nodes.length - 1; i >= 0; i--) { + if (this.getItemType(item) === "Separator") { + if (nodes[i].localName === "menuseparator") + return nodes[i]; + } + else if (nodes[i].getAttribute("label") === item.label) + return nodes[i]; + } + return null; + }, + + // Returns "Item", "Menu", or "Separator". + getItemType: function (item) { + // Could use instanceof here, but that would require accessing the loader + // that created the item, and I don't want to A) somehow search through the + // this.loaders list to find it, and B) assume there are any live loaders at + // all. + return /^\[object (Item|Menu|Separator)/.exec(item.toString())[1]; + }, + + // Returns a wrapper around a new loader: { loader, cm, unload, globalScope }. + // loader is a Cuddlefish sandboxed loader, cm is the context menu module, + // globalScope is the context menu module's global scope, and unload is a + // function that unloads the loader and associated resources. + newLoader: function () { + const self = this; + let loader = Loader(module); + let wrapper = { + loader: loader, + cm: loader.require("sdk/context-menu"), + globalScope: loader.sandbox("sdk/context-menu"), + unload: function () { + loader.unload(); + let idx = self.loaders.indexOf(wrapper); + if (idx < 0) + throw new Error("Test error: tried to unload nonexistent loader"); + self.loaders.splice(idx, 1); + } + }; + this.loaders.push(wrapper); + return wrapper; + }, + + // Returns true if the number of presentItems crosses the overflow threshold. + shouldOverflow: function (presentItems) { + return presentItems.length > + (this.loaders.length ? + this.loaders[0].loader.require("sdk/preferences/service"). + get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT) : + OVERFLOW_THRESH_DEFAULT); + }, + + // Opens the context menu on the current page. If targetNode is null, the + // menu is opened in the top-left corner. onShowncallback is passed the + // popup. + showMenu: function(targetNode, onshownCallback) { + function sendEvent() { + this.delayedEventListener(this.browserWindow, "popupshowing", + function (e) { + let popup = e.target; + onshownCallback.call(this, popup); + }, false); + + let rect = targetNode ? + targetNode.getBoundingClientRect() : + { left: 0, top: 0, width: 0, height: 0 }; + let contentWin = this.browserWindow.content; + contentWin. + QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDOMWindowUtils). + sendMouseEvent("contextmenu", + rect.left + (rect.width / 2), + rect.top + (rect.height / 2), + 2, 1, 0); + } + + // If a new tab or window has not yet been opened, open a new tab now. For + // some reason using the tab already opened when the test starts causes + // leaks. See bug 566351 for details. + if (!targetNode && !this.oldSelectedTab && !this.oldBrowserWindow) { + this.oldSelectedTab = this.tabBrowser.selectedTab; + this.tab = this.tabBrowser.addTab("about:blank"); + let browser = this.tabBrowser.getBrowserForTab(this.tab); + + this.delayedEventListener(browser, "load", function () { + this.tabBrowser.selectedTab = this.tab; + sendEvent.call(this); + }, true); + } + else + sendEvent.call(this); + }, + + // Opens a new browser window. The window will be closed automatically when + // done() is called. + withNewWindow: function (onloadCallback) { + let win = this.browserWindow.OpenBrowserWindow(); + this.delayedEventListener(win, "load", onloadCallback, true); + this.oldBrowserWindow = this.browserWindow; + this.browserWindow = win; + }, + + // Opens a new tab with our test page in the current window. The tab will + // be closed automatically when done() is called. + withTestDoc: function (onloadCallback) { + this.oldSelectedTab = this.tabBrowser.selectedTab; + this.tab = this.tabBrowser.addTab(TEST_DOC_URL); + let browser = this.tabBrowser.getBrowserForTab(this.tab); + + this.delayedEventListener(browser, "load", function () { + this.tabBrowser.selectedTab = this.tab; + onloadCallback.call(this, browser.contentWindow, browser.contentDocument); + }, true); + } +}; diff --git a/tools/addon-sdk-1.12/test/test-cortex.js b/tools/addon-sdk-1.12/test/test-cortex.js new file mode 100644 index 0000000..ab3d194 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-cortex.js @@ -0,0 +1,122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// vim:set ts=2 sw=2 sts=2 + +"use strict"; + +var Cortex = require("sdk/deprecated/cortex").Cortex; + +exports["test property changes propagate"] = function (assert) { + var source = { + _foo: "secret", + get foo() { + return this._foo; + }, + set foo(value) { + this._foo = value; + }, + get getOnly() { + return this._foo; + }, + set setOnly(value) { + this._setOnly = value; + }, + bar: "public", + method: function method(a, b) { + return this._foo + a + b + } + }; + var fixture = Cortex(source); + + assert.ok(!('_foo' in fixture), + "properties that start with `_` are omitted"); + assert.equal(fixture.foo, "secret", "get accessor alias works"); + fixture.foo = "new secret"; + assert.equal(fixture.foo, "new secret", "set accessor alias works"); + assert.equal(source.foo, "new secret", "accessor delegates to the source"); + assert.equal(fixture.bar, "public", "data property alias works"); + fixture.bar = "bar"; + assert.equal(source.bar, "bar", "data property change propagates"); + source.bar = "foo" + assert.equal(fixture.bar, "foo", "data property change probagets back"); + assert.equal(fixture.method("a", "b"), "new secretab", + "public methods are callable"); + assert.equal(fixture.method.call({ _foo: "test" }, " a,", "b"), + "new secret a,b", + "`this` pseudo-variable can not be passed through call."); + assert.equal(fixture.method.apply({ _foo: "test" }, [" a,", "b"]), + "new secret a,b", + "`this` pseudo-variable can not be passed through apply."); + assert.equal(fixture.getOnly, source._foo, + "getter returned property of wrapped object"); + fixture.setOnly = 'bar' + assert.equal(source._setOnly, 'bar', "setter modified wrapped object") +}; + + +exports["test immunity of inheritance"] = function(assert) { + function Type() {} + Type.prototype = { + constructor: Type, + _bar: 2, + bar: 3, + get_Foo: function getFoo() { + return this._foo; + } + } + var source = Object.create(Type.prototype, { + _foo: { value: 'secret' }, + getBar: { value: function get_Bar() { + return this.bar + }}, + get_Bar: { value: function getBar() { + return this._bar + }} + }); + + var fixture = Cortex(source); + + assert.ok(Cortex({}, null, Type.prototype) instanceof Type, + "if custom prototype is providede cortex will inherit from it"); + assert.ok(fixture instanceof Type, + "if no prototype is given cortex inherits from object's prototype"); + + source.bar += 1; + assert.notEqual(fixture.bar, source.bar, + "chages of properties don't propagate to non-aliases"); + assert.equal(fixture.getBar(), source.bar, + "methods accessing public properties are bound to the source"); + + fixture._bar += 1; + assert.notEqual(fixture._bar, source._bar, + "changes of non aliased properties don't propagate"); + assert.equal(fixture.get_Bar(), source._bar, + "methods accessing privates are bound to the source"); + assert.notEqual(fixture.get_Foo(), source._foo, + "prototoype methods are not bound to the source"); +} + +exports["test customized public properties"] = function(assert) { + var source = { + _a: 'a', + b: 'b', + get: function get(name) { + return this[name]; + } + }; + + var fixture = Cortex(source, ['_a', 'get']); + fixture._a += "#change"; + + + assert.ok(!("b" in fixture), "non-public own property is not defined"); + assert.equal(fixture.get("b"), source.b, + "public methods preserve access to the private properties"); + assert.equal(fixture._a, source._a, + "custom public property changes propagate"); +} + +//if (require.main == module) + require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-cuddlefish.js b/tools/addon-sdk-1.12/test/test-cuddlefish.js new file mode 100644 index 0000000..f160469 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-cuddlefish.js @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +const { Loader, Require, unload, override } = require('sdk/loader/cuddlefish'); +const packaging = require('@loader/options'); + +exports['test loader'] = function(assert) { + var prints = []; + function print(message) { + prints.push(message); + } + + let options = JSON.parse(JSON.stringify(packaging)); + + let loader = Loader(override(options, { + globals: { + print: print, + foo: 1 + } + })); + let require = Require(loader, module); + + var fixture = require('./loader/fixture'); + + assert.equal(fixture.foo, 1, 'custom globals must work.'); + assert.equal(fixture.bar, 2, 'exports are set'); + + assert.equal(prints[0], 'testing', 'global print must be injected.'); + + var unloadsCalled = ''; + + require("sdk/system/unload").when(function(reason) { + assert.equal(reason, 'test', 'unload reason is passed'); + unloadsCalled += 'a'; + }); + require('sdk/system/unload.js').when(function() { + unloadsCalled += 'b'; + }); + + unload(loader, 'test'); + + assert.equal(unloadsCalled, 'ba', + 'loader.unload() must call listeners in LIFO order.'); +}; + +require('test').run(exports); diff --git a/tools/addon-sdk-1.12/test/test-deprecate.js b/tools/addon-sdk-1.12/test/test-deprecate.js new file mode 100644 index 0000000..b1e0f32 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-deprecate.js @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const deprecate = require("sdk/util/deprecate"); +var { Loader } = require("sdk/test/loader"); + +function LoaderWithHookedConsole() { + let errors = []; + let loader = Loader(module, { + console: Object.create(console, { + error: { value: function(error) { + errors.push(error); + }} + }) + }); + + return { + loader: loader, + deprecate: loader.require("sdk/util/deprecate"), + errors: errors + } +} + +exports["test Deprecate Usage"] = function testDeprecateUsage(assert) { + let { loader, deprecate, errors } = LoaderWithHookedConsole(); + + function functionIsDeprecated() { + deprecate.deprecateUsage("foo"); + } + + functionIsDeprecated(); + + assert.equal(errors.length, 1, "only one error is dispatched"); + + let msg = errors[0]; + assert.ok(msg.indexOf("foo") !== -1, + "message contains the given message"); + assert.ok(msg.indexOf("functionIsDeprecated") !== -1, + "message contains name of the caller function"); + assert.ok(msg.indexOf(module.uri) !== -1, + "message contains URI of the caller module"); + + loader.unload(); +} + +exports["test Deprecate Function"] = function testDeprecateFunction(assert) { + let { loader, deprecate, errors } = LoaderWithHookedConsole(); + + let self = {}; + let arg1 = "foo"; + let arg2 = {}; + + function originalFunction(a1, a2) { + assert.equal(this, self); + assert.equal(a1, arg1); + assert.equal(a2, arg2); + }; + + let deprecateFunction = deprecate.deprecateFunction(originalFunction, + "bar"); + + deprecateFunction.call(self, arg1, arg2); + + assert.equal(errors.length, 1, + "only one error is dispatched"); + + let msg = errors[0]; + assert.ok(msg.indexOf("bar") !== -1, "message contains the given message"); + assert.ok(msg.indexOf("testDeprecateFunction") !== -1, + "message contains name of the caller function"); + assert.ok(msg.indexOf(module.uri) !== -1, + "message contains URI of the caller module"); + + loader.unload(); +} + +require("test").run(exports) diff --git a/tools/addon-sdk-1.12/test/test-deprecated-list.js b/tools/addon-sdk-1.12/test/test-deprecated-list.js new file mode 100644 index 0000000..4b5f598 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-deprecated-list.js @@ -0,0 +1,196 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +function assertList(test, array, list) { + for (let i = 0, ii = array.length; i < ii; i < ii, i++) { + test.assertEqual( + array.length, + list.length, + 'list must contain same amount of elements as array' + ); + test.assertEqual( + 'List(' + array + ')', + list + '', + 'toString must output array like result' + ); + test.assert( + i in list, + 'must contain element with index: ' + i + ); + test.assertEqual( + array[i], + list[i], + 'element with index: ' + i + ' should match' + ); + } +} + +const { List } = require('sdk/deprecated/list'); + +exports['test:test for'] = function(test) { + let fixture = List(3, 2, 1); + + test.assertEqual(3, fixture.length, 'length is 3'); + let i = 0; + for (let key in fixture) { + test.assertEqual(i++, key, 'key should match'); + } +}; + +exports['test:test for each'] = function(test) { + let fixture = new List(3, 2, 1); + + test.assertEqual(3, fixture.length, 'length is 3'); + let i = 3; + for each (let value in fixture) { + test.assertEqual(i--, value, 'value should match'); + } +}; + +exports['test:test toString'] = function(test) { + let fixture = List(3, 2, 1); + + test.assertEqual( + 'List(3,2,1)', + fixture + '', + 'toString must output array like result' + ) +}; + +exports['test:test constructor with apply'] = function(test) { + let array = ['a', 'b', 'c']; + let fixture = List.apply(null, array); + + test.assertEqual( + 3, + fixture.length, + 'should have applied arguments' + ); +}; + +exports['test:direct element access'] = function(test) { + let array = [1, 'foo', 2, 'bar', {}, 'bar', function a() {}, test, 1]; + let fixture = List.apply(null, array); + array.splice(5, 1); + array.splice(7, 1); + + test.assertEqual( + array.length, + fixture.length, + 'list should omit duplicate elements' + ); + + test.assertEqual( + 'List(' + array + ')', + fixture.toString(), + 'elements should not be rearranged' + ); + + for (let key in array) { + test.assert(key in fixture,'should contain key for index:' + key); + test.assertEqual( + array[key], + fixture[key], + 'values should match for: ' + key + ); + } +}; + +exports['test:removing adding elements'] = function(test) { + let array = [1, 'foo', 2, 'bar', {}, 'bar', function a() {}, test, 1]; + let fixture = List.compose({ + add: function() this._add.apply(this, arguments), + remove: function() this._remove.apply(this, arguments), + clear: function() this._clear() + }).apply(null, array); + array.splice(5, 1); + array.splice(7, 1); + + assertList(test, array, fixture); + + array.splice(array.indexOf(2), 1); + fixture.remove(2); + assertList(test, array, fixture); + + array.splice(array.indexOf('foo'), 1); + fixture.remove('foo'); + array.splice(array.indexOf(1), 1); + fixture.remove(1); + array.push('foo'); + fixture.add('foo'); + assertList(test, array, fixture); + + array.splice(0); + fixture.clear(0); + assertList(test, array, fixture); + + array.push(1, 'foo', 2, 'bar', 3); + fixture.add(1); + fixture.add('foo'); + fixture.add(2); + fixture.add('bar'); + fixture.add(3); + + assertList(test, array, fixture); +}; + +exports['test: remove does not leave invalid numerical properties'] = function(test) { + let fixture = List.compose({ + remove: function() this._remove.apply(this, arguments), + }).apply(null, [1, 2, 3]); + + fixture.remove(1); + test.assertEqual(fixture[fixture.length], undefined); +} + +exports['test:add list item from Iterator'] = function(test) { + let array = [1, 2, 3, 4], sum = 0, added = false; + + let fixture = List.compose({ + add: function() this._add.apply(this, arguments), + }).apply(null, array); + + for each (let item in fixture) { + sum += item; + + if (!added) { + fixture.add(5); + added = true; + } + } + + test.assertEqual(sum, 1 + 2 + 3 + 4); +}; + +exports['test:remove list item from Iterator'] = function(test) { + let array = [1, 2, 3, 4], sum = 0; + + let fixture = List.compose({ + remove: function() this._remove.apply(this, arguments), + }).apply(null, array); + + for each (let item in fixture) { + sum += item; + fixture.remove(item); + } + + test.assertEqual(sum, 1 + 2 + 3 + 4); +}; + +exports['test:clear list from Iterator'] = function(test) { + let array = [1, 2, 3, 4], sum = 0; + + let fixture = List.compose({ + clear: function() this._clear() + }).apply(null, array); + + for each (let item in fixture) { + sum += item; + fixture.clear(); + } + + test.assertEqual(sum, 1 + 2 + 3 + 4); +}; diff --git a/tools/addon-sdk-1.12/test/test-dom.js b/tools/addon-sdk-1.12/test/test-dom.js new file mode 100644 index 0000000..87c8372 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-dom.js @@ -0,0 +1,88 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const events = require("sdk/dom/events"); +const { activeBrowserWindow: { document } } = require("sdk/deprecated/window-utils"); +const window = document.window; + +exports["test on / emit"] = function (assert, done) { + let element = document.createElement("div"); + events.on(element, "click", function listener(event) { + assert.equal(event.target, element, "event has correct target"); + events.removeListener(element, "click", listener); + done(); + }); + + events.emit(element, "click", { + category: "MouseEvents", + settings: [ + true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null + ] + }); +}; + +exports["test remove"] = function (assert, done) { + let element = document.createElement("span"); + let l1 = 0; + let l2 = 0; + let options = { + category: "MouseEvents", + settings: [ + true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null + ] + }; + + events.on(element, "click", function listener1(event) { + l1 ++; + assert.equal(event.target, element, "event has correct target"); + events.removeListener(element, "click", listener1); + }); + + events.on(element, "click", function listener2(event) { + l2 ++; + if (l1 < l2) { + assert.equal(l1, 1, "firs listener was called and then romeved"); + events.removeListener(element, "click", listener2); + done(); + } + events.emit(element, "click", options); + }); + + events.emit(element, "click", options); +}; + +exports["test once"] = function (assert, done) { + let element = document.createElement("h1"); + let l1 = 0; + let l2 = 0; + let options = { + category: "MouseEvents", + settings: [ + true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null + ] + }; + + + events.once(element, "click", function listener(event) { + assert.equal(event.target, element, "event target is a correct element"); + l1 ++; + }); + + events.on(element, "click", function listener(event) { + l2 ++; + if (l2 > 3) { + events.removeListener(element, "click", listener); + assert.equal(event.target, element, "event has correct target"); + assert.equal(l1, 1, "once was called only once"); + done(); + } + events.emit(element, "click", options); + }); + + events.emit(element, "click", options); +} + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-environment.js b/tools/addon-sdk-1.12/test/test-environment.js new file mode 100644 index 0000000..413997f --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-environment.js @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +const { env } = require('sdk/system/environment'); +const { Cc, Ci } = require('chrome'); +const { get, set, exists } = Cc['@mozilla.org/process/environment;1']. + getService(Ci.nsIEnvironment); + +exports['test exists'] = function(assert) { + assert.equal('PATH' in env, exists('PATH'), + 'PATH environment variable is defined'); + assert.equal('FOO1' in env, exists('FOO1'), + 'FOO1 environment variable is not defined'); + set('FOO1', 'foo'); + assert.equal('FOO1' in env, true, + 'FOO1 environment variable was set'); + set('FOO1', null); + assert.equal('FOO1' in env, false, + 'FOO1 environment variable was unset'); +}; + +exports['test get'] = function(assert) { + assert.equal(env.PATH, get('PATH'), 'PATH env variable matches'); + assert.equal(env.BAR2, undefined, 'BAR2 env variable is not defined'); + set('BAR2', 'bar'); + assert.equal(env.BAR2, 'bar', 'BAR2 env variable was set'); + set('BAR2', null); + assert.equal(env.BAR2, undefined, 'BAR2 env variable was unset'); +}; + +exports['test set'] = function(assert) { + assert.equal(get('BAZ3'), '', 'BAZ3 env variable is not set'); + assert.equal(env.BAZ3, undefined, 'BAZ3 is not set'); + env.BAZ3 = 'baz'; + assert.equal(env.BAZ3, get('BAZ3'), 'BAZ3 env variable is set'); + assert.equal(get('BAZ3'), 'baz', 'BAZ3 env variable was set to "baz"'); +}; + +exports['test unset'] = function(assert) { + env.BLA4 = 'bla'; + assert.equal(env.BLA4, 'bla', 'BLA4 env varibale is set'); + delete env.BLA4; + assert.equal(env.BLA4, undefined, 'BLA4 env variable is unset'); + assert.equal('BLA4' in env, false, 'BLA4 env variable no longer exists' ); +}; + +require('test').run(exports); diff --git a/tools/addon-sdk-1.12/test/test-errors.js b/tools/addon-sdk-1.12/test/test-errors.js new file mode 100644 index 0000000..91ffd78 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-errors.js @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var errors = require("sdk/deprecated/errors"); + +exports.testCatchAndLog = function(test) { + var caught = []; + function dummyLog(e) { caught.push(e); } + + var wrapped = errors.catchAndLog(function(x) { + throw Error("blah" + x + this); + }, + "boop", + dummyLog); + test.assertEqual(wrapped.call("hi", 1), "boop", + "exceptions should be trapped, def. resp. returned"); + test.assertEqual(caught.length, 1, + "logging function should be called"); + test.assertEqual(caught[0].message, "blah1hi", + "args and this should be passed to wrapped func"); +}; + +exports.testCatchAndLogProps = function(test) { + var caught = []; + function dummyLog(e) { caught.push(e); } + + var thing = { + foo: function(x) { throw Error("nowai" + x); }, + bar: function() { throw Error("blah"); }, + baz: function() { throw Error("fnarg"); } + }; + + errors.catchAndLogProps(thing, "foo", "ugh", dummyLog); + + test.assertEqual(thing.foo(1), "ugh", + "props should be wrapped"); + test.assertEqual(caught.length, 1, + "logging function should be called"); + test.assertEqual(caught[0].message, "nowai1", + "args should be passed to wrapped func"); + test.assertRaises(function() { thing.bar(); }, + "blah", + "non-wrapped props should be wrapped"); + + errors.catchAndLogProps(thing, ["bar", "baz"], "err", dummyLog); + test.assert((thing.bar() == thing.baz()) && + (thing.bar() == "err"), + "multiple props should be wrapped if array passed in"); +}; + +exports.testCatchAndReturn = function(test) { + var wrapped = errors.catchAndReturn(function(x) { + if (x == 1) + return "one"; + if (x == 2) + throw new Error("two"); + return this + x; + }); + + test.assertEqual(wrapped(1).returnValue, "one", + "arg should be passed; return value should be returned"); + test.assert(wrapped(2).exception, "exception should be returned"); + test.assertEqual(wrapped(2).exception.message, "two", "message is correct"); + test.assert(wrapped(2).exception.fileName.indexOf("test-errors.js") != -1, + "filename is present"); + test.assert(wrapped(2).exception.stack, "stack is available"); + test.assertEqual(wrapped.call("hi", 3).returnValue, "hi3", + "`this` should be set correctly"); +}; diff --git a/tools/addon-sdk-1.12/test/test-event-core.js b/tools/addon-sdk-1.12/test/test-event-core.js new file mode 100644 index 0000000..2d94137 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-event-core.js @@ -0,0 +1,216 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +const { on, once, off, emit, count, amass } = require('sdk/event/core'); +const { Loader } = require('sdk/test/loader'); + +exports['test add a listener'] = function(assert) { + let events = [ { name: 'event#1' }, 'event#2' ]; + let target = { name: 'target' }; + + on(target, 'message', function(message) { + assert.equal(this, target, 'this is a target object'); + assert.equal(message, events.shift(), 'message is emitted event'); + }); + emit(target, 'message', events[0]); + emit(target, 'message', events[0]); +}; + +exports['test that listener is unique per type'] = function(assert) { + let actual = [] + let target = {} + function listener() { actual.push(1) } + on(target, 'message', listener); + on(target, 'message', listener); + on(target, 'message', listener); + on(target, 'foo', listener); + on(target, 'foo', listener); + + emit(target, 'message'); + assert.deepEqual([ 1 ], actual, 'only one message listener added'); + emit(target, 'foo'); + assert.deepEqual([ 1, 1 ], actual, 'same listener added for other event'); +}; + +exports['test event type matters'] = function(assert) { + let target = { name: 'target' } + on(target, 'message', function() { + assert.fail('no event is expected'); + }); + on(target, 'done', function() { + assert.pass('event is emitted'); + }); + emit(target, 'foo') + emit(target, 'done'); +}; + +exports['test all arguments are pasesd'] = function(assert) { + let foo = { name: 'foo' }, bar = 'bar'; + let target = { name: 'target' }; + on(target, 'message', function(a, b) { + assert.equal(a, foo, 'first argument passed'); + assert.equal(b, bar, 'second argument passed'); + }); + emit(target, 'message', foo, bar); +}; + +exports['test no side-effects in emit'] = function(assert) { + let target = { name: 'target' }; + on(target, 'message', function() { + assert.pass('first listener is called'); + on(target, 'message', function() { + assert.fail('second listener is called'); + }); + }); + emit(target, 'message'); +}; + +exports['test order of propagation'] = function(assert) { + let actual = []; + let target = { name: 'target' }; + on(target, 'message', function() { actual.push(1); }); + on(target, 'message', function() { actual.push(2); }); + on(target, 'message', function() { actual.push(3); }); + emit(target, 'message'); + assert.deepEqual([ 1, 2, 3 ], actual, 'called in order they were added'); +}; + +exports['test remove a listener'] = function(assert) { + let target = { name: 'target' }; + let actual = []; + on(target, 'message', function listener() { + actual.push(1); + on(target, 'message', function() { + off(target, 'message', listener); + actual.push(2); + }) + }); + + emit(target, 'message'); + assert.deepEqual([ 1 ], actual, 'first listener called'); + emit(target, 'message'); + assert.deepEqual([ 1, 1, 2 ], actual, 'second listener called'); + + emit(target, 'message'); + assert.deepEqual([ 1, 1, 2, 2, 2 ], actual, 'first listener removed'); +}; + +exports['test remove all listeners for type'] = function(assert) { + let actual = []; + let target = { name: 'target' } + on(target, 'message', function() { actual.push(1); }); + on(target, 'message', function() { actual.push(2); }); + on(target, 'message', function() { actual.push(3); }); + on(target, 'bar', function() { actual.push('b') }); + off(target, 'message'); + + emit(target, 'message'); + emit(target, 'bar'); + + assert.deepEqual([ 'b' ], actual, 'all message listeners were removed'); +}; + +exports['test remove all listeners'] = function(assert) { + let actual = []; + let target = { name: 'target' } + on(target, 'message', function() { actual.push(1); }); + on(target, 'message', function() { actual.push(2); }); + on(target, 'message', function() { actual.push(3); }); + on(target, 'bar', function() { actual.push('b') }); + off(target); + + emit(target, 'message'); + emit(target, 'bar'); + + assert.deepEqual([], actual, 'all listeners events were removed'); +}; + +exports['test falsy arguments are fine'] = function(assert) { + let type, listener, actual = []; + let target = { name: 'target' } + on(target, 'bar', function() { actual.push(0) }); + + off(target, 'bar', listener); + emit(target, 'bar'); + assert.deepEqual([ 0 ], actual, '3rd bad ard will keep listeners'); + + off(target, type); + emit(target, 'bar'); + assert.deepEqual([ 0, 0 ], actual, '2nd bad arg will keep listener'); + + off(target, type, listener); + emit(target, 'bar'); + assert.deepEqual([ 0, 0, 0 ], actual, '2nd&3rd bad args will keep listener'); +}; + +exports['test error handling'] = function(assert) { + let target = Object.create(null); + let error = Error('boom!'); + + on(target, 'message', function() { throw error; }) + on(target, 'error', function(boom) { + assert.equal(boom, error, 'thrown exception causes error event'); + }); + emit(target, 'message'); +}; + +exports['test unhandled errors'] = function(assert) { + let exceptions = []; + let loader = Loader(module, { + console: Object.create(console, { + exception: { value: function(e) { + exceptions.push(e); + }} + }) + }); + let { emit, on } = loader.require('sdk/event/core'); + let target = {}; + let boom = Error('Boom!'); + let drax = Error('Draax!!'); + + on(target, 'message', function() { throw boom; }); + + emit(target, 'message'); + assert.ok(~String(exceptions[0]).indexOf('Boom!'), + 'unhandled exception is logged'); + + on(target, 'error', function() { throw drax; }); + emit(target, 'message'); + assert.ok(~String(exceptions[1]).indexOf('Draax!'), + 'error in error handler is logged'); +}; + +exports['test count'] = function(assert) { + let target = {}; + + assert.equal(count(target, 'foo'), 0, 'no listeners for "foo" events'); + on(target, 'foo', function() {}); + assert.equal(count(target, 'foo'), 1, 'listener registered'); + on(target, 'foo', function() {}, 2, 'another listener registered'); + off(target) + assert.equal(count(target, 'foo'), 0, 'listeners unregistered'); +}; + +exports['test emit.lazy'] = function(assert) { + let target = {}, boom = Error('boom!'), errors = [], actual = [] + + on(target, 'error', function error(e) errors.push(e)) + + on(target, 'a', function() 1); + on(target, 'a', function() {}); + on(target, 'a', function() 2); + on(target, 'a', function() { throw boom }); + on(target, 'a', function() 3); + + for each (let value in emit.lazy(target, 'a')) + actual.push(value); + + assert.deepEqual(actual, [ 1, undefined, 2, 3 ], + 'all results were collected'); + assert.deepEqual(errors, [ boom ], 'errors reporetd'); +}; + +require('test').run(exports); diff --git a/tools/addon-sdk-1.12/test/test-event-target.js b/tools/addon-sdk-1.12/test/test-event-target.js new file mode 100644 index 0000000..d972454 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-event-target.js @@ -0,0 +1,167 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +const { emit } = require('sdk/event/core'); +const { EventTarget } = require('sdk/event/target'); +const { Loader } = require('sdk/test/loader'); + +exports['test add a listener'] = function(assert) { + let events = [ { name: 'event#1' }, 'event#2' ]; + let target = EventTarget(); + + target.on('message', function(message) { + assert.equal(this, target, 'this is a target object'); + assert.equal(message, events.shift(), 'message is emitted event'); + }); + + emit(target, 'message', events[0]); + emit(target, 'message', events[0]); +}; + +exports['test pass in listeners'] = function(assert) { + let actual = [ ]; + let target = EventTarget({ + onMessage: function onMessage(message) { + assert.equal(this, target, 'this is an event target'); + actual.push(1); + }, + onFoo: null, + onbla: function() { + assert.fail('`onbla` is not supposed to be called'); + } + }); + target.on('message', function(message) { + assert.equal(this, target, 'this is an event target'); + actual.push(2); + }); + + emit(target, 'message'); + emit(target, 'missing'); + + assert.deepEqual([ 1, 2 ], actual, 'all listeners trigerred in right order'); +}; + +exports['test that listener is unique per type'] = function(assert) { + let actual = [] + let target = EventTarget(); + function listener() { actual.push(1) } + target.on('message', listener); + target.on('message', listener); + target.on('message', listener); + target.on('foo', listener); + target.on('foo', listener); + + emit(target, 'message'); + assert.deepEqual([ 1 ], actual, 'only one message listener added'); + emit(target, 'foo'); + assert.deepEqual([ 1, 1 ], actual, 'same listener added for other event'); +}; + +exports['test event type matters'] = function(assert) { + let target = EventTarget(); + target.on('message', function() { + assert.fail('no event is expected'); + }); + target.on('done', function() { + assert.pass('event is emitted'); + }); + + emit(target, 'foo'); + emit(target, 'done'); +}; + +exports['test all arguments are pasesd'] = function(assert) { + let foo = { name: 'foo' }, bar = 'bar'; + let target = EventTarget(); + target.on('message', function(a, b) { + assert.equal(a, foo, 'first argument passed'); + assert.equal(b, bar, 'second argument passed'); + }); + emit(target, 'message', foo, bar); +}; + +exports['test no side-effects in emit'] = function(assert) { + let target = EventTarget(); + target.on('message', function() { + assert.pass('first listener is called'); + target.on('message', function() { + assert.fail('second listener is called'); + }); + }); + emit(target, 'message'); +}; + +exports['test order of propagation'] = function(assert) { + let actual = []; + let target = EventTarget(); + target.on('message', function() { actual.push(1); }); + target.on('message', function() { actual.push(2); }); + target.on('message', function() { actual.push(3); }); + emit(target, 'message'); + assert.deepEqual([ 1, 2, 3 ], actual, 'called in order they were added'); +}; + +exports['test remove a listener'] = function(assert) { + let target = EventTarget(); + let actual = []; + target.on('message', function listener() { + actual.push(1); + target.on('message', function() { + target.removeListener('message', listener); + actual.push(2); + }) + }); + + target.removeListener('message'); // must do nothing. + emit(target, 'message'); + assert.deepEqual([ 1 ], actual, 'first listener called'); + emit(target, 'message'); + assert.deepEqual([ 1, 1, 2 ], actual, 'second listener called'); + emit(target, 'message'); + assert.deepEqual([ 1, 1, 2, 2, 2 ], actual, 'first listener removed'); +}; + +exports['test error handling'] = function(assert) { + let target = EventTarget(); + let error = Error('boom!'); + + target.on('message', function() { throw error; }) + target.on('error', function(boom) { + assert.equal(boom, error, 'thrown exception causes error event'); + }); + emit(target, 'message'); +}; + +exports['test unhandled errors'] = function(assert) { + let exceptions = []; + let loader = Loader(module); + let { emit } = loader.require('sdk/event/core'); + let { EventTarget } = loader.require('sdk/event/target'); + Object.defineProperties(loader.sandbox('sdk/event/core'), { + console: { value: { + exception: function(e) { + exceptions.push(e); + } + }} + }); + let target = EventTarget(); + let boom = Error('Boom!'); + let drax = Error('Draax!!'); + + target.on('message', function() { throw boom; }); + + emit(target, 'message'); + assert.ok(~String(exceptions[0]).indexOf('Boom!'), + 'unhandled exception is logged'); + + target.on('error', function() { throw drax; }); + emit(target, 'message'); + assert.ok(~String(exceptions[1]).indexOf('Draax!'), + 'error in error handler is logged'); +}; + +require('test').run(exports); + diff --git a/tools/addon-sdk-1.12/test/test-events.js b/tools/addon-sdk-1.12/test/test-events.js new file mode 100644 index 0000000..a1f87bf --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-events.js @@ -0,0 +1,267 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +// Exposing private methods as public in order to test +const EventEmitter = require('sdk/deprecated/events').EventEmitter.compose({ + listeners: function(type) this._listeners(type), + emit: function() this._emit.apply(this, arguments), + emitOnObject: function() this._emitOnObject.apply(this, arguments), + removeAllListeners: function(type) this._removeAllListeners(type) +}); + +exports['test:add listeners'] = function(test) { + let e = new EventEmitter(); + + let events_new_listener_emited = []; + let times_hello_emited = 0; + + e.on("newListener", function (event, listener) { + events_new_listener_emited.push(event) + }) + + e.on("hello", function (a, b) { + times_hello_emited += 1 + test.assertEqual("a", a) + test.assertEqual("b", b) + test.assertEqual(this, e, '`this` pseudo-variable is bound to instance'); + }) + + e.emit("hello", "a", "b") +}; + +exports['test:removeListener'] = function(test) { + let count = 0; + + function listener1 () { + count++; + } + function listener2 () { + count++; + } + + // test adding and removing listener + let e1 = new EventEmitter(); + test.assertEqual(0, e1.listeners('hello').length); + e1.on("hello", listener1); + test.assertEqual(1, e1.listeners('hello').length); + test.assertEqual(listener1, e1.listeners('hello')[0]); + e1.removeListener("hello", listener1); + test.assertEqual(0, e1.listeners('hello').length); + e1.emit("hello", ""); + test.assertEqual(0, count); + + // test adding one listener and removing another which was not added + let e2 = new EventEmitter(); + test.assertEqual(0, e2.listeners('hello').length); + e2.on("hello", listener1); + test.assertEqual(1, e2.listeners('hello').length); + e2.removeListener("hello", listener2); + test.assertEqual(1, e2.listeners('hello').length); + test.assertEqual(listener1, e2.listeners('hello')[0]); + e2.emit("hello", ""); + test.assertEqual(1, count); + + // test adding 2 listeners, and removing one + let e3 = new EventEmitter(); + test.assertEqual(0, e3.listeners('hello').length); + e3.on("hello", listener1); + test.assertEqual(1, e3.listeners('hello').length); + e3.on("hello", listener2); + test.assertEqual(2, e3.listeners('hello').length); + e3.removeListener("hello", listener1); + test.assertEqual(1, e3.listeners('hello').length); + test.assertEqual(listener2, e3.listeners('hello')[0]); + e3.emit("hello", ""); + test.assertEqual(2, count); +}; + +exports['test:removeAllListeners'] = function(test) { + let count = 0; + + function listener1 () { + count++; + } + function listener2 () { + count++; + } + + // test adding a listener and removing all of that type + let e1 = new EventEmitter(); + e1.on("hello", listener1); + test.assertEqual(1, e1.listeners('hello').length); + e1.removeAllListeners("hello"); + test.assertEqual(0, e1.listeners('hello').length); + e1.emit("hello", ""); + test.assertEqual(0, count); + + // test adding a listener and removing all of another type + let e2 = new EventEmitter(); + e2.on("hello", listener1); + test.assertEqual(1, e2.listeners('hello').length); + e2.removeAllListeners('goodbye'); + test.assertEqual(1, e2.listeners('hello').length); + e2.emit("hello", ""); + test.assertEqual(1, count); + + // test adding 1+ listeners and removing all of that type + let e3 = new EventEmitter(); + e3.on("hello", listener1); + test.assertEqual(1, e3.listeners('hello').length); + e3.on("hello", listener2); + test.assertEqual(2, e3.listeners('hello').length); + e3.removeAllListeners("hello"); + test.assertEqual(0, e3.listeners('hello').length); + e3.emit("hello", ""); + test.assertEqual(1, count); + + // test adding 2 listeners for 2 types and removing all listeners + let e4 = new EventEmitter(); + e4.on("hello", listener1); + test.assertEqual(1, e4.listeners('hello').length); + e4.on('goodbye', listener2); + test.assertEqual(1, e4.listeners('goodbye').length); + e4.emit("goodbye", ""); + e4.removeAllListeners(); + test.assertEqual(0, e4.listeners('hello').length); + test.assertEqual(0, e4.listeners('goodbye').length); + e4.emit("hello", ""); + e4.emit("goodbye", ""); + test.assertEqual(2, count); +}; + +exports['test: modify in emit'] = function(test) { + let callbacks_called = [ ]; + let e = new EventEmitter(); + + function callback1() { + callbacks_called.push("callback1"); + e.on("foo", callback2); + e.on("foo", callback3); + e.removeListener("foo", callback1); + } + function callback2() { + callbacks_called.push("callback2"); + e.removeListener("foo", callback2); + } + function callback3() { + callbacks_called.push("callback3"); + e.removeListener("foo", callback3); + } + + e.on("foo", callback1); + test.assertEqual(1, e.listeners("foo").length); + + e.emit("foo"); + test.assertEqual(2, e.listeners("foo").length); + test.assertEqual(1, callbacks_called.length); + test.assertEqual('callback1', callbacks_called[0]); + + e.emit("foo"); + test.assertEqual(0, e.listeners("foo").length); + test.assertEqual(3, callbacks_called.length); + test.assertEqual('callback1', callbacks_called[0]); + test.assertEqual('callback2', callbacks_called[1]); + test.assertEqual('callback3', callbacks_called[2]); + + e.emit("foo"); + test.assertEqual(0, e.listeners("foo").length); + test.assertEqual(3, callbacks_called.length); + test.assertEqual('callback1', callbacks_called[0]); + test.assertEqual('callback2', callbacks_called[1]); + test.assertEqual('callback3', callbacks_called[2]); + + e.on("foo", callback1); + e.on("foo", callback2); + test.assertEqual(2, e.listeners("foo").length); + e.removeAllListeners("foo"); + test.assertEqual(0, e.listeners("foo").length); + + // Verify that removing callbacks while in emit allows emits to propagate to + // all listeners + callbacks_called = [ ]; + + e.on("foo", callback2); + e.on("foo", callback3); + test.assertEqual(2, e.listeners("foo").length); + e.emit("foo"); + test.assertEqual(2, callbacks_called.length); + test.assertEqual('callback2', callbacks_called[0]); + test.assertEqual('callback3', callbacks_called[1]); + test.assertEqual(0, e.listeners("foo").length); +}; + +exports['test:adding same listener'] = function(test) { + function foo() {} + let e = new EventEmitter(); + e.on("foo", foo); + e.on("foo", foo); + test.assertEqual( + 1, + e.listeners("foo").length, + "listener reregistration is ignored" + ); +} + +exports['test:errors are reported if listener throws'] = function(test) { + let e = new EventEmitter(), + reported = false; + e.on('error', function(e) reported = true) + e.on('boom', function() { throw new Error('Boom!') }); + e.emit('boom', 3); + test.assert(reported, 'error should be reported through event'); +}; + +exports['test:emitOnObject'] = function(test) { + let e = new EventEmitter(); + + e.on("foo", function() { + test.assertEqual(this, e, "`this` should be emitter"); + }); + e.emitOnObject(e, "foo"); + + e.on("bar", function() { + test.assertEqual(this, obj, "`this` should be other object"); + }); + let obj = {}; + e.emitOnObject(obj, "bar"); +}; + +exports['test:once'] = function(test) { + let e = new EventEmitter(); + let called = false; + + e.once('foo', function(value) { + test.assert(!called, "listener called only once"); + test.assertEqual(value, "bar", "correct argument was passed"); + }); + + e.emit('foo', 'bar'); + e.emit('foo', 'baz'); +}; + +exports["test:removing once"] = function(test) { + let e = require("sdk/deprecated/events").EventEmitterTrait.create(); + e.once("foo", function() { test.pass("listener was called"); }); + e.once("error", function() { test.fail("error event was emitted"); }); + e._emit("foo", "bug-656684"); +}; + +// Bug 726967: Ensure that `emit` doesn't do an infinite loop when `error` +// listener throws an exception +exports['test:emitLoop'] = function(test) { + let e = new EventEmitter(); + + e.on("foo", function() { + throw new Error("foo"); + }); + + e.on("error", function() { + throw new Error("error"); + }); + e.emit("foo"); + + test.pass("emit didn't looped"); +}; diff --git a/tools/addon-sdk-1.12/test/test-file.js b/tools/addon-sdk-1.12/test/test-file.js new file mode 100644 index 0000000..33344b2 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-file.js @@ -0,0 +1,273 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { pathFor } = require('sdk/system'); +const file = require("sdk/io/file"); +const url = require("sdk/url"); + +const byteStreams = require("sdk/io/byte-streams"); +const textStreams = require("sdk/io/text-streams"); + +const ERRORS = { + FILE_NOT_FOUND: /^path does not exist: .+$/, + NOT_A_DIRECTORY: /^path is not a directory: .+$/, + NOT_A_FILE: /^path is not a file: .+$/, +}; + +// Use profile directory to list / read / write files. +const profilePath = pathFor('ProfD'); +const fileNameInProfile = 'compatibility.ini'; +const dirNameInProfile = 'extensions'; +const filePathInProfile = file.join(profilePath, fileNameInProfile); +const dirPathInProfile = file.join(profilePath, dirNameInProfile); + +exports.testDirName = function(test) { + test.assertEqual(file.dirname(dirPathInProfile), profilePath, + "file.dirname() of dir should return parent dir"); + + test.assertEqual(file.dirname(filePathInProfile), profilePath, + "file.dirname() of file should return its dir"); + + let dir = profilePath; + while (dir) + dir = file.dirname(dir); + + test.assertEqual(dir, "", + "dirname should return empty string when dir has no parent"); +}; + +exports.testBasename = function(test) { + // Get the top-most path -- the path with no basename. E.g., on Unix-like + // systems this will be /. We'll use it below to build up some test paths. + // We have to go to this trouble because file.join() needs a legal path as a + // base case; join("foo", "bar") doesn't work unfortunately. + let topPath = profilePath; + let parentPath = file.dirname(topPath); + while (parentPath) { + topPath = parentPath; + parentPath = file.dirname(topPath); + } + + let path = topPath; + test.assertEqual(file.basename(path), "", + "basename should work on paths with no components"); + + path = file.join(path, "foo"); + test.assertEqual(file.basename(path), "foo", + "basename should work on paths with a single component"); + + path = file.join(path, "bar"); + test.assertEqual(file.basename(path), "bar", + "basename should work on paths with multiple components"); +}; + +exports.testList = function(test) { + let list = file.list(profilePath); + let found = [ true for each (name in list) + if (name === fileNameInProfile) ]; + + if (found.length > 1) + test.fail("a dir can't contain two files of the same name!"); + test.assertEqual(found[0], true, "file.list() should work"); + + test.assertRaises(function() { + file.list(filePathInProfile); + }, ERRORS.NOT_A_DIRECTORY, "file.list() on non-dir should raise error"); + + test.assertRaises(function() { + file.list(file.join(dirPathInProfile, "does-not-exist")); + }, ERRORS.FILE_NOT_FOUND, "file.list() on nonexistent should raise error"); +}; + +exports.testRead = function(test) { + let contents = file.read(filePathInProfile); + test.assertMatches(contents, /Compatibility/, + "file.read() should work"); + + test.assertRaises(function() { + file.read(file.join(dirPathInProfile, "does-not-exists")); + }, ERRORS.FILE_NOT_FOUND, "file.read() on nonexistent file should throw"); + + test.assertRaises(function() { + file.read(dirPathInProfile); + }, ERRORS.NOT_A_FILE, "file.read() on dir should throw"); +}; + +exports.testJoin = function(test) { + let baseDir = file.dirname(filePathInProfile); + + test.assertEqual(file.join(baseDir, fileNameInProfile), + filePathInProfile, "file.join() should work"); +}; + +exports.testOpenNonexistentForRead = function (test) { + var filename = file.join(profilePath, 'does-not-exists'); + test.assertRaises(function() { + file.open(filename); + }, ERRORS.FILE_NOT_FOUND, "file.open() on nonexistent file should throw"); + + test.assertRaises(function() { + file.open(filename, "r"); + }, ERRORS.FILE_NOT_FOUND, "file.open('r') on nonexistent file should throw"); + + test.assertRaises(function() { + file.open(filename, "zz"); + }, ERRORS.FILE_NOT_FOUND, "file.open('zz') on nonexistent file should throw"); +}; + +exports.testOpenNonexistentForWrite = function (test) { + let filename = file.join(profilePath, 'open.txt'); + + let stream = file.open(filename, "w"); + stream.close(); + + test.assert(file.exists(filename), + "file.exists() should return true after file.open('w')"); + file.remove(filename); + test.assert(!file.exists(filename), + "file.exists() should return false after file.remove()"); + + stream = file.open(filename, "rw"); + stream.close(); + + test.assert(file.exists(filename), + "file.exists() should return true after file.open('rw')"); + file.remove(filename); + test.assert(!file.exists(filename), + "file.exists() should return false after file.remove()"); +}; + +exports.testOpenDirectory = function (test) { + let dir = dirPathInProfile; + test.assertRaises(function() { + file.open(dir); + }, ERRORS.NOT_A_FILE, "file.open() on directory should throw"); + + test.assertRaises(function() { + file.open(dir, "w"); + }, ERRORS.NOT_A_FILE, "file.open('w') on directory should throw"); +}; + +exports.testOpenTypes = function (test) { + let filename = file.join(profilePath, 'open-types.txt'); + + + // Do the opens first to create the data file. + var stream = file.open(filename, "w"); + test.assert(stream instanceof textStreams.TextWriter, + "open(w) should return a TextWriter"); + stream.close(); + + stream = file.open(filename, "wb"); + test.assert(stream instanceof byteStreams.ByteWriter, + "open(wb) should return a ByteWriter"); + stream.close(); + + stream = file.open(filename); + test.assert(stream instanceof textStreams.TextReader, + "open() should return a TextReader"); + stream.close(); + + stream = file.open(filename, "r"); + test.assert(stream instanceof textStreams.TextReader, + "open(r) should return a TextReader"); + stream.close(); + + stream = file.open(filename, "b"); + test.assert(stream instanceof byteStreams.ByteReader, + "open(b) should return a ByteReader"); + stream.close(); + + stream = file.open(filename, "rb"); + test.assert(stream instanceof byteStreams.ByteReader, + "open(rb) should return a ByteReader"); + stream.close(); + + file.remove(filename); +}; + +exports.testMkpathRmdir = function (test) { + let basePath = profilePath; + let dirs = []; + for (let i = 0; i < 3; i++) + dirs.push("test-file-dir"); + + let paths = []; + for (let i = 0; i < dirs.length; i++) { + let args = [basePath].concat(dirs.slice(0, i + 1)); + paths.unshift(file.join.apply(null, args)); + } + + for (let i = 0; i < paths.length; i++) { + test.assert(!file.exists(paths[i]), + "Sanity check: path should not exist: " + paths[i]); + } + + file.mkpath(paths[0]); + test.assert(file.exists(paths[0]), "mkpath should create path: " + paths[0]); + + for (let i = 0; i < paths.length; i++) { + file.rmdir(paths[i]); + test.assert(!file.exists(paths[i]), + "rmdir should remove path: " + paths[i]); + } +}; + +exports.testMkpathTwice = function (test) { + let dir = profilePath; + let path = file.join(dir, "test-file-dir"); + test.assert(!file.exists(path), + "Sanity check: path should not exist: " + path); + file.mkpath(path); + test.assert(file.exists(path), "mkpath should create path: " + path); + file.mkpath(path); + test.assert(file.exists(path), + "After second mkpath, path should still exist: " + path); + file.rmdir(path); + test.assert(!file.exists(path), "rmdir should remove path: " + path); +}; + +exports.testMkpathExistingNondirectory = function (test) { + var fname = file.join(profilePath, 'conflict.txt'); + file.open(fname, "w").close(); + test.assert(file.exists(fname), "File should exist"); + test.assertRaises(function() file.mkpath(fname), + /^The path already exists and is not a directory: .+$/, + "mkpath on file should raise error"); + file.remove(fname); +}; + +exports.testRmdirNondirectory = function (test) { + var fname = file.join(profilePath, 'not-a-dir') + file.open(fname, "w").close(); + test.assert(file.exists(fname), "File should exist"); + test.assertRaises(function() { + file.rmdir(fname); + }, ERRORS.NOT_A_DIRECTORY, "rmdir on file should raise error"); + file.remove(fname); + test.assert(!file.exists(fname), "File should not exist"); + test.assertRaises(function () file.rmdir(fname), + ERRORS.FILE_NOT_FOUND, + "rmdir on non-existing file should raise error"); +}; + +exports.testRmdirNonempty = function (test) { + let dir = profilePath; + let path = file.join(dir, "test-file-dir"); + test.assert(!file.exists(path), + "Sanity check: path should not exist: " + path); + file.mkpath(path); + let filePath = file.join(path, "file"); + file.open(filePath, "w").close(); + test.assert(file.exists(filePath), + "Sanity check: path should exist: " + filePath); + test.assertRaises(function () file.rmdir(path), + /^The directory is not empty: .+$/, + "rmdir on non-empty directory should raise error"); + file.remove(filePath); + file.rmdir(path); + test.assert(!file.exists(path), "Path should not exist"); +}; diff --git a/tools/addon-sdk-1.12/test/test-frame-utils.js b/tools/addon-sdk-1.12/test/test-frame-utils.js new file mode 100644 index 0000000..6225f00 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-frame-utils.js @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +const { open } = require('sdk/window/utils'); +const { create } = require('sdk/frame/utils'); + +exports['test frame creation'] = function(assert, done) { + let window = open('data:text/html;charset=utf-8,Window'); + window.addEventListener('DOMContentLoaded', function windowReady() { + + let frame = create(window.document); + + assert.equal(frame.getAttribute('type'), 'content', + 'frame type is content'); + assert.ok(frame.contentWindow, 'frame has contentWindow'); + assert.equal(frame.contentWindow.location.href, 'about:blank', + 'by default "about:blank" is loaded'); + assert.equal(frame.docShell.allowAuth, false, 'auth disabled by default'); + assert.equal(frame.docShell.allowJavascript, false, 'js disabled by default'); + assert.equal(frame.docShell.allowPlugins, false, + 'plugins disabled by default'); + window.close(); + done(); + }, false); +}; + +exports['test fram has js disabled by default'] = function(assert, done) { + let window = open('data:text/html;charset=utf-8,window'); + window.addEventListener('DOMContentLoaded', function windowReady() { + window.removeEventListener('DOMContentLoaded', windowReady, false); + let frame = create(window.document, { + uri: 'data:text/html;charset=utf-8,<script>document.documentElement.innerHTML' + + '= "J" + "S"</script>', + }); + frame.contentWindow.addEventListener('DOMContentLoaded', function ready() { + frame.contentWindow.removeEventListener('DOMContentLoaded', ready, false); + assert.ok(!~frame.contentDocument.documentElement.innerHTML.indexOf('JS'), + 'JS was executed'); + + window.close(); + done(); + }, false); + + }, false); +}; + +exports['test frame with js enabled'] = function(assert, done) { + let window = open('data:text/html;charset=utf-8,window'); + window.addEventListener('DOMContentLoaded', function windowReady() { + window.removeEventListener('DOMContentLoaded', windowReady, false); + let frame = create(window.document, { + uri: 'data:text/html;charset=utf-8,<script>document.documentElement.innerHTML' + + '= "J" + "S"</script>', + allowJavascript: true + }); + frame.contentWindow.addEventListener('DOMContentLoaded', function ready() { + frame.contentWindow.removeEventListener('DOMContentLoaded', ready, false); + assert.ok(~frame.contentDocument.documentElement.innerHTML.indexOf('JS'), + 'JS was executed'); + + window.close(); + done(); + }, false); + + }, false); +}; + +require('test').run(exports); diff --git a/tools/addon-sdk-1.12/test/test-functional.js b/tools/addon-sdk-1.12/test/test-functional.js new file mode 100644 index 0000000..81d36e9 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-functional.js @@ -0,0 +1,170 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { setTimeout } = require('sdk/timers'); +const utils = require('sdk/lang/functional'); +const { invoke, defer, curry, compose, memoize, once, delay, wrap } = utils; + +exports['test forwardApply'] = function(assert) { + function sum(b, c) this.a + b + c + assert.equal(invoke(sum, [2, 3], { a: 1 }), 6, + 'passed arguments and pseoude-variable are used'); + + assert.equal(invoke(sum.bind({ a: 2 }), [2, 3], { a: 1 }), 7, + 'bounded `this` pseoudo variable is used'); +} + +exports['test deferred function'] = function(assert, done) { + let nextTurn = false; + function sum(b, c) { + assert.ok(nextTurn, 'enqueued is called in next turn of event loop'); + assert.equal(this.a + b + c, 6, + 'passed arguments an pseoude-variable are used'); + done(); + } + + let fixture = { a: 1, method: defer(sum) } + fixture.method(2, 3); + nextTurn = true; +}; + +exports['test curry function'] = function(assert) { + function sum(b, c) this.a + b + c; + + let foo = { a : 5 }; + + foo.sum7 = curry(sum, 7); + foo.sum8and4 = curry(sum, 8, 4); + + assert.equal(foo.sum7(2), 14, 'curry one arguments works'); + + assert.equal(foo.sum8and4(), 17, 'curry both arguments works'); +}; + +exports['test compose'] = function(assert) { + let greet = function(name) { return 'hi: ' + name; }; + let exclaim = function(sentence) { return sentence + '!'; }; + + assert.equal(compose(exclaim, greet)('moe'), 'hi: moe!', + 'can compose a function that takes another'); + + assert.equal(compose(greet, exclaim)('moe'), 'hi: moe!', + 'in this case, the functions are also commutative'); + + let target = { + name: 'Joe', + greet: compose(function exclaim(sentence) { + return sentence + '!' + }, function(title) { + return 'hi : ' + title + ' ' + this.name; + }) + } + + assert.equal(target.greet('Mr'), 'hi : Mr Joe!', + 'this can be passed in'); + assert.equal(target.greet.call({ name: 'Alex' }, 'Dr'), 'hi : Dr Alex!', + 'this can be applied'); + + let single = compose(function(value) { + return value + ':suffix'; + }); + + assert.equal(single('text'), 'text:suffix', 'works with single function'); + + let identity = compose(); + assert.equal(identity('bla'), 'bla', 'works with zero functions'); +}; + +exports['test wrap'] = function(assert) { + let greet = function(name) { return 'hi: ' + name; }; + let backwards = wrap(greet, function(f, name) { + return f(name) + ' ' + name.split('').reverse().join(''); + }); + + assert.equal(backwards('moe'), 'hi: moe eom', + 'wrapped the saluation function'); + + let inner = function () { return 'Hello '; }; + let target = { + name: 'Matteo', + hi: wrap(inner, function(f) { return f() + this.name; }) + }; + + assert.equal(target.hi(), 'Hello Matteo', 'works with this'); + + function noop() { }; + let wrapped = wrap(noop, function(f) { + return Array.slice(arguments); + }); + + let actual = wrapped([ 'whats', 'your' ], 'vector', 'victor'); + assert.deepEqual(actual, [ noop, ['whats', 'your'], 'vector', 'victor' ], + 'works with fancy stuff'); +}; + +exports['test memoize'] = function(assert) { + function fib(n) n < 2 ? n : fib(n - 1) + fib(n - 2) + let fibnitro = memoize(fib); + + assert.equal(fib(10), 55, + 'a memoized version of fibonacci produces identical results'); + assert.equal(fibnitro(10), 55, + 'a memoized version of fibonacci produces identical results'); + + function o(key, value) { return value; }; + let oo = memoize(o), v1 = {}, v2 = {}; + + + assert.equal(oo(1, v1), v1, 'returns value back'); + assert.equal(oo(1, v2), v1, 'memoized by a first argument'); + assert.equal(oo(2, v2), v2, 'returns back value if not memoized'); + assert.equal(oo(2), v2, 'memoized new value'); + assert.notEqual(oo(1), oo(2), 'values do not override'); + assert.equal(o(3, v2), oo(2, 3), 'returns same value as un-memoized'); + + let get = memoize(function(attribute) this[attribute]) + let target = { name: 'Bob', get: get } + + assert.equal(target.get('name'), 'Bob', 'has correct `this`'); + assert.equal(target.get.call({ name: 'Jack' }, 'name'), 'Bob', + 'name is memoized') + assert.equal(get('name'), 'Bob', 'once memoized can be called without this'); +}; + +exports['test delay'] = function(assert, done) { + let delayed = false; + delay(function() { + assert.ok(delayed, 'delayed the function'); + done(); + }, 1); + delayed = true; +}; + +exports['test delay with this'] = function(assert, done) { + let context = {} + delay.call(context, function(name) { + assert.equal(this, context, 'this was passed in'); + assert.equal(name, 'Tom', 'argument was passed in'); + done(); + }, 10, 'Tom'); +} + +exports['test once'] = function(assert) { + let n = 0; + let increment = once(function() { n ++; }); + + increment(); + increment(); + + assert.equal(n, 1, 'only incremented once'); + + let target = { state: 0, update: once(function() this.state ++ ) }; + + target.update(); + target.update(); + + assert.equal(target.state, 1, 'this was passed in and called only once'); +}; + +require('test').run(exports); diff --git a/tools/addon-sdk-1.12/test/test-globals.js b/tools/addon-sdk-1.12/test/test-globals.js new file mode 100644 index 0000000..1d8caf0 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-globals.js @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Object.defineProperty(this, "global", { value: this }); + +exports.testGlobals = function(test) { + // the only globals in module scope should be: + // module, exports, require, dump, console + test.assertObject(module, "have 'module', good"); + test.assertObject(exports, "have 'exports', good"); + test.assertFunction(require, "have 'require', good"); + test.assertFunction(dump, "have 'dump', good"); + test.assertObject(console, "have 'console', good"); + + // in particular, these old globals should no longer be present + test.assert(!('packaging' in global), "no 'packaging', good"); + test.assert(!('memory' in global), "no 'memory', good"); + + test.assertMatches(module.uri, /test-globals\.js$/, + 'should contain filename'); +}; diff --git a/tools/addon-sdk-1.12/test/test-heritage.js b/tools/addon-sdk-1.12/test/test-heritage.js new file mode 100644 index 0000000..493ffdd --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-heritage.js @@ -0,0 +1,375 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Class, extend, mix, obscure } = require('sdk/core/heritage'); + +exports['test extend'] = function(assert) { + let ancestor = { a: 1 }; + let descendant = extend(ancestor, { + b: 2, + get c() { return 3 }, + d: function() { return 4 } + }); + + assert.ok(ancestor.isPrototypeOf(descendant), + 'descendant inherits from ancestor'); + assert.ok(descendant.b, 2, 'proprety was implemented'); + assert.ok(descendant.c, 3, 'getter was implemented'); + assert.ok(descendant.d(), 4, 'method was implemented'); + + /* Will be fixed once Bug 674195 is shipped. + assert.ok(Object.isFrozen(descendant), + 'extend returns frozen objects'); + */ +}; + +exports['test mix'] = function(assert) { + let ancestor = { a: 1 } + let mixed = mix(extend(ancestor, { b: 1, c: 1 }), { c: 2 }, { d: 3 }); + + assert.deepEqual(JSON.parse(JSON.stringify(mixed)), { b: 1, c: 2, d: 3 }, + 'properties mixed as expected'); + assert.ok(ancestor.isPrototypeOf(mixed), + 'first arguments ancestor is ancestor of result'); +}; + +exports['test obscure'] = function(assert) { + let fixture = mix({ a: 1 }, obscure({ b: 2 })); + + assert.equal(fixture.a, 1, 'a property is included'); + assert.equal(fixture.b, 2, 'b proprety is included'); + assert.ok(!Object.getOwnPropertyDescriptor(fixture, 'b').enumerable, + 'obscured properties are non-enumerable'); +}; + +exports['test inheritance'] = function(assert) { + let Ancestor = Class({ + name: 'ancestor', + method: function () { + return 'hello ' + this.name; + } + }); + + assert.ok(Ancestor() instanceof Ancestor, + 'can be instantiated without new'); + assert.ok(new Ancestor() instanceof Ancestor, + 'can also be instantiated with new'); + assert.ok(Ancestor() instanceof Class, + 'if ancestor not specified than defaults to Class'); + assert.ok(Ancestor.prototype.extends, Class.prototype, + 'extends of prototype points to ancestors prototype'); + + + assert.equal(Ancestor().method(), 'hello ancestor', + 'instance inherits defined properties'); + + let Descendant = Class({ + extends: Ancestor, + name: 'descendant' + }); + + assert.ok(Descendant() instanceof Descendant, + 'instantiates correctly'); + assert.ok(Descendant() instanceof Ancestor, + 'Inherits for passed `extends`'); + assert.equal(Descendant().method(), 'hello descendant', + 'propreties inherited'); +}; + +exports['test prototype immutability'] = function(assert) { + let Foo = Class({ + name: 'hello', + rename: function rename(name) { + this.name = name; + } + }); + + /* Disable until release with Bug 674195 fix is shipped + assert.ok(Object.isFrozen(Foo), 'Foo is frozen'); + assert.ok(Object.isFrozen(Foo.prototype), 'Foo prototype is frozen'); + assert.ok(Object.isFrozen(Object.getPrototypeOf(Foo.prototype)), + 'Class.prototype is frozen'); + assert.equal(Object.getPrototypeOf(Object.getPrototypeOf(Foo.prototype)), + null, 'prototype of Class.prototype is null'); + */ + + assert.throws(function() { + var override = function() {}; + Foo.prototype.extend = override; + if (Foo.prototype.extend !== override) + throw Error('Property was not set'); + }, 'Can not change prototype properties'); + + assert.throws(function() { + Foo.prototype.foo = 'bar'; + if (Foo.prototype.foo !== 'bar') + throw Error('Property was not set'); + }, 'Can not add prototype properties'); + + assert.throws(function() { + delete Foo.prototype.name; + if ('name' in Foo.prototype) + throw Error('Property was not deleted'); + }, 'Can not remove prototype properties'); + + var Bar = Class({ + extends: Foo, + rename: function rename() { + return this.name; + } + }); + + assert.equal(Bar().rename(), 'hello', + 'properties may be overided on decedents'); +}; + +exports['test immunity against __proto__'] = function(assert) { + let Foo = Class({ name: 'foo', hacked: false }); + + let Bar = Class({ extends: Foo, name: 'bar' }); + + assert.throws(function() { + Foo.prototype.__proto__ = { hacked: true }; + if (Foo() instanceof Base && !Foo().hacked) + throw Error('can not change prototype chain'); + }, 'prototype chain is immune to __proto__ hacks'); + + assert.throws(function() { + Foo.prototype.__proto__ = { hacked: true }; + if (Bar() instanceof Foo && !Bar().hacked) + throw Error('can not change prototype chain'); + }, 'prototype chain of decedants immune to __proto__ hacks'); +}; + +exports['test instance mutability'] = function(assert) { + let Foo = Class({ + name: 'foo', + initialize: function initialize(number) { + this.number = number; + } + }); + + let f1 = Foo(); + + assert.throws(function() { + f1.name = 'f1'; + if (f1.name !== 'f1') + throw Error('Property was not set'); + }, 'can not change prototype properties'); + + f1.alias = 'f1'; + assert.equal(f1.alias, 'f1', 'instance is mutable'); + + delete f1.alias; + assert.ok(!('alias' in f1), 'own properties are deletable'); + + f1.initialize(1); + assert.equal(f1.number, 1, 'method can mutate instances own properties'); +}; + +exports['test super'] = function(assert) { + var Foo = Class({ + initialize: function initialize(options) { + this.name = options.name; + } + }); + + var Bar = Class({ + extends: Foo, + initialize: function Bar(options) { + Foo.prototype.initialize.call(this, options); + this.type = 'bar'; + } + }); + + var bar = Bar({ name: 'test' }); + + assert.equal(bar.type, 'bar', 'bar initializer was called'); + assert.equal(bar.name, 'test', 'bar initializer called Foo initializer'); +}; + +exports['test initialize'] = function(assert) { + var Dog = Class({ + initialize: function initialize(name) { + this.name = name; + }, + type: 'dog', + bark: function bark() { + return 'Ruff! Ruff!' + } + }); + + var fluffy = Dog('Fluffy'); // instatiation + assert.ok(fluffy instanceof Dog, + 'instanceof works as expected'); + assert.ok(fluffy instanceof Class, + 'inherits form Class if not specified otherwise'); + assert.ok(fluffy.name, 'fluffy', + 'initialize unless specified otherwise'); +}; + +exports['test complements regular inheritace'] = function(assert) { + let Base = Class({ name: 'base' }); + + function Type() { + // ... + } + Type.prototype = Object.create(Base.prototype); + Type.prototype.run = function() { + // ... + }; + + let value = new Type(); + + assert.ok(value instanceof Type, 'creates instance of Type'); + assert.ok(value instanceof Base, 'inherits from Base'); + assert.equal(value.name, 'base', 'inherits properties from Base'); + + + let SubType = Class({ + extends: Type, + sub: 'type' + }); + + let fixture = SubType(); + + assert.ok(fixture instanceof Base, 'is instance of Base'); + assert.ok(fixture instanceof Type, 'is instance of Type'); + assert.ok(fixture instanceof SubType, 'is instance of SubType'); + + assert.equal(fixture.sub, 'type', 'proprety is defined'); + assert.equal(fixture.run, Type.prototype.run, 'proprety is inherited'); + assert.equal(fixture.name, 'base', 'inherits base properties'); +}; + +exports['test extends object'] = function(assert) { + let prototype = { constructor: function() { return this; }, name: 'me' }; + let Foo = Class({ + extends: prototype, + value: 2 + }); + let foo = new Foo(); + + assert.ok(foo instanceof Foo, 'instance of Foo'); + assert.ok(!(foo instanceof Class), 'is not instance of Class'); + assert.ok(prototype.isPrototypeOf(foo), 'inherits from given prototype'); + assert.equal(Object.getPrototypeOf(Foo.prototype), prototype, + 'contsructor prototype inherits from extends option'); + assert.equal(foo.value, 2, 'property is defined'); + assert.equal(foo.name, 'me', 'prototype proprety is inherited'); +}; + + +var HEX = Class({ + hex: function hex() { + return '#' + this.color; + } +}); + +var RGB = Class({ + red: function red() { + return parseInt(this.color.substr(0, 2), 16); + }, + green: function green() { + return parseInt(this.color.substr(2, 2), 16); + }, + blue: function blue() { + return parseInt(this.color.substr(4, 2), 16); + } +}); + +var CMYK = Class({ + black: function black() { + var color = Math.max(Math.max(this.red(), this.green()), this.blue()); + return (1 - color / 255).toFixed(4); + }, + magenta: function magenta() { + var K = this.black(); + return (((1 - this.green() / 255).toFixed(4) - K) / (1 - K)).toFixed(4); + }, + yellow: function yellow() { + var K = this.black(); + return (((1 - this.blue() / 255).toFixed(4) - K) / (1 - K)).toFixed(4); + }, + cyan: function cyan() { + var K = this.black(); + return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).toFixed(4); + } +}); + +var Color = Class({ + implements: [ HEX, RGB, CMYK ], + initialize: function initialize(color) { + this.color = color; + } +}); + +exports['test composition'] = function(assert) { + var pink = Color('FFC0CB'); + + assert.equal(pink.red(), 255, 'red() works'); + assert.equal(pink.green(), 192, 'green() works'); + assert.equal(pink.blue(), 203, 'blue() works'); + + assert.equal(pink.magenta(), 0.2471, 'magenta() works'); + assert.equal(pink.yellow(), 0.2039, 'yellow() works'); + assert.equal(pink.cyan(), 0.0000, 'cyan() works'); + + assert.ok(pink instanceof Color, 'is instance of Color'); + assert.ok(pink instanceof Class, 'is instance of Class'); +}; + +var Point = Class({ + initialize: function initialize(x, y) { + this.x = x; + this.y = y; + }, + toString: function toString() { + return this.x + ':' + this.y; + } +}) + +var Pixel = Class({ + extends: Point, + implements: [ Color ], + initialize: function initialize(x, y, color) { + Color.prototype.initialize.call(this, color); + Point.prototype.initialize.call(this, x, y); + }, + toString: function toString() { + return this.hex() + '@' + Point.prototype.toString.call(this) + } +}); + +exports['test compostion with inheritance'] = function(assert) { + var pixel = Pixel(11, 23, 'CC3399'); + + assert.equal(pixel.toString(), '#CC3399@11:23', 'stringifies correctly'); + assert.ok(pixel instanceof Pixel, 'instance of Pixel'); + assert.ok(pixel instanceof Point, 'instance of Point'); +}; + +exports['test composition with objects'] = function(assert) { + var A = { a: 1, b: 1 }; + var B = Class({ b: 2, c: 2 }); + var C = { c: 3 }; + var D = { d: 4 }; + + var ABCD = Class({ + implements: [ A, B, C, D ], + e: 5 + }); + + var f = ABCD(); + + assert.equal(f.a, 1, 'inherits A.a'); + assert.equal(f.b, 2, 'inherits B.b overrides A.b'); + assert.equal(f.c, 3, 'inherits C.c overrides B.c'); + assert.equal(f.d, 4, 'inherits D.d'); + assert.equal(f.e, 5, 'implements e'); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-hidden-frame.js b/tools/addon-sdk-1.12/test/test-hidden-frame.js new file mode 100644 index 0000000..cf99df5 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-hidden-frame.js @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +let tests = {}, hiddenFrames, HiddenFrame; + +tests.testFrame = function(test) { + let url = "data:text/html;charset=utf-8,<!DOCTYPE%20html>"; + test.waitUntilDone(); + let hiddenFrame = hiddenFrames.add(HiddenFrame({ + onReady: function () { + test.assertEqual(this.element.contentWindow.location, "about:blank", + "HiddenFrame loads about:blank by default."); + + function onDOMReady() { + hiddenFrame.element.removeEventListener("DOMContentLoaded", onDOMReady, + false); + test.assertEqual(hiddenFrame.element.contentWindow.location, url, + "HiddenFrame loads the specified content."); + test.done(); + } + this.element.addEventListener("DOMContentLoaded", onDOMReady, false); + this.element.setAttribute("src", url); + } + })); +}; + +let hiddenFrameSupported = true; + +try { + hiddenFrames = require("sdk/frame/hidden-frame"); + HiddenFrame = hiddenFrames.HiddenFrame; +} +catch(ex if ex.message == [ + "The hidden-frame module currently supports only Firefox and Thunderbird. ", + "In the future, we would like it to support other applications, however. ", + "Please see https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more ", + "information." + ].join("")) { + hiddenFrameSupported = false; +} + +if (hiddenFrameSupported) { + for (let test in tests) + exports[test] = tests[test]; +} +else { + exports.testHiddenFrameNotSupported = function(test) { + test.pass("The hidden-frame module is not supported on this app."); + } +} diff --git a/tools/addon-sdk-1.12/test/test-hotkeys.js b/tools/addon-sdk-1.12/test/test-hotkeys.js new file mode 100644 index 0000000..7dc590f --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-hotkeys.js @@ -0,0 +1,162 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Hotkey } = require("sdk/hotkeys"); +const { keyDown } = require("sdk/dom/events/keys"); +const { Loader } = require('sdk/test/loader'); +const timer = require("sdk/timers"); +const winUtils = require("sdk/deprecated/window-utils"); + +exports["test hotkey: function key"] = function(assert, done) { + var element = winUtils.activeBrowserWindow.document.documentElement; + var showHotKey = Hotkey({ + combo: "f1", + onPress: function() { + assert.pass("first callback is called"); + keyDown(element, "f2"); + showHotKey.destroy(); + } + }); + + var hideHotKey = Hotkey({ + combo: "f2", + onPress: function() { + assert.pass("second callback is called"); + hideHotKey.destroy(); + done(); + } + }); + + keyDown(element, "f1"); +}; + +exports["test hotkey: accel alt shift"] = function(assert, done) { + var element = winUtils.activeBrowserWindow.document.documentElement; + var showHotKey = Hotkey({ + combo: "accel-shift-6", + onPress: function() { + assert.pass("first callback is called"); + keyDown(element, "accel-alt-shift-6"); + showHotKey.destroy(); + } + }); + + var hideHotKey = Hotkey({ + combo: "accel-alt-shift-6", + onPress: function() { + assert.pass("second callback is called"); + hideHotKey.destroy(); + done(); + } + }); + + keyDown(element, "accel-shift-6"); +}; + +exports["test hotkey meta & control"] = function(assert, done) { + var element = winUtils.activeBrowserWindow.document.documentElement; + var showHotKey = Hotkey({ + combo: "meta-3", + onPress: function() { + assert.pass("first callback is called"); + keyDown(element, "alt-control-shift-b"); + showHotKey.destroy(); + } + }); + + var hideHotKey = Hotkey({ + combo: "Ctrl-Alt-Shift-B", + onPress: function() { + assert.pass("second callback is called"); + hideHotKey.destroy(); + done(); + } + }); + + keyDown(element, "meta-3"); +}; + +exports["test hotkey: control-1 / meta--"] = function(assert, done) { + var element = winUtils.activeBrowserWindow.document.documentElement; + var showHotKey = Hotkey({ + combo: "control-1", + onPress: function() { + assert.pass("first callback is called"); + keyDown(element, "meta--"); + showHotKey.destroy(); + } + }); + + var hideHotKey = Hotkey({ + combo: "meta--", + onPress: function() { + assert.pass("second callback is called"); + hideHotKey.destroy(); + done(); + } + }); + + keyDown(element, "control-1"); +}; + +exports["test invalid combos"] = function(assert) { + assert.throws(function() { + Hotkey({ + combo: "d", + onPress: function() {} + }); + }, "throws if no modifier is present"); + assert.throws(function() { + Hotkey({ + combo: "alt", + onPress: function() {} + }); + }, "throws if no key is present"); + assert.throws(function() { + Hotkey({ + combo: "alt p b", + onPress: function() {} + }); + }, "throws if more then one key is present"); +}; + +exports["test no exception on unmodified keypress"] = function(assert) { + var element = winUtils.activeBrowserWindow.document.documentElement; + var someHotkey = Hotkey({ + combo: "control-alt-1", + onPress: function() { + } + }); + keyDown(element, "a"); + assert.pass("No exception throw, unmodified keypress passed"); +}; + +exports["test hotkey: automatic destroy"] = function(assert, done) { + // Hacky way to be able to create unloadable modules via makeSandboxedLoader. + let loader = Loader(module); + + var called = false; + var element = loader.require("sdk/deprecated/window-utils").activeBrowserWindow.document.documentElement; + var hotkey = loader.require("sdk/hotkeys").Hotkey({ + combo: "accel-shift-x", + onPress: function() { + called = true; + } + }); + + // Unload the module so that previous hotkey is automatically destroyed + loader.unload(); + + // Ensure that the hotkey is really destroyed + keyDown(element, "accel-shift-x"); + + timer.setTimeout(function () { + assert.ok(!called, "Hotkey is destroyed and not called."); + done(); + }, 0); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-httpd.js b/tools/addon-sdk-1.12/test/test-httpd.js new file mode 100644 index 0000000..1ab9522 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-httpd.js @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const port = 8099; +const file = require("sdk/io/file"); +const { pathFor } = require("sdk/system"); + +exports.testBasicHTTPServer = function(test) { + let basePath = pathFor("TmpD"); + let filePath = file.join(basePath, 'test-httpd.txt'); + let content = "This is the HTTPD test file.\n"; + let fileStream = file.open(filePath, 'w'); + fileStream.write(content); + fileStream.close(); + + let { startServerAsync } = require("sdk/test/httpd"); + let srv = startServerAsync(port, basePath); + + test.waitUntilDone(); + + // Request this very file. + let Request = require('sdk/request').Request; + Request({ + url: "http://localhost:" + port + "/test-httpd.txt", + onComplete: function (response) { + test.assertEqual(response.text, content); + done(); + } + }).get(); + + function done() { + srv.stop(function() { + test.done(); + }); + } +}; + +exports.testDynamicServer = function (test) { + let content = "This is the HTTPD test file.\n"; + + let { startServerAsync } = require("sdk/test/httpd"); + let srv = startServerAsync(port); + + // See documentation here: + //http://doxygen.db48x.net/mozilla/html/interfacensIHttpServer.html#a81fc7e7e29d82aac5ce7d56d0bedfb3a + //http://doxygen.db48x.net/mozilla/html/interfacensIHttpRequestHandler.html + srv.registerPathHandler("/test-httpd.txt", function handle(request, response) { + // Add text content type, only to avoid error in `Request` API + response.setHeader("Content-Type", "text/plain", false); + response.write(content); + }); + + test.waitUntilDone(); + + // Request this very file. + let Request = require('sdk/request').Request; + Request({ + url: "http://localhost:" + port + "/test-httpd.txt", + onComplete: function (response) { + test.assertEqual(response.text, content); + done(); + } + }).get(); + + function done() { + srv.stop(function() { + test.done(); + }); + } + +} diff --git a/tools/addon-sdk-1.12/test/test-keyboard-observer.js b/tools/addon-sdk-1.12/test/test-keyboard-observer.js new file mode 100644 index 0000000..091ec73 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-keyboard-observer.js @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { keyPress } = require("sdk/dom/events/keys"); +const { Loader } = require("sdk/test/loader"); +const timer = require("sdk/timers"); + +exports["test unload keyboard observer"] = function(assert, done) { + let loader = Loader(module); + let element = loader.require("sdk/deprecated/window-utils"). + activeBrowserWindow.document.documentElement; + let observer = loader.require("sdk/keyboard/observer"). + observer; + let called = 0; + + observer.on("keypress", function () { called++; }); + + // dispatching "keypress" event to trigger observer listeners. + keyPress(element, "accel-%"); + + // Unload the module. + loader.unload(); + + // dispatching "keypress" even once again. + keyPress(element, "accel-%"); + + // Enqueuing asserts to make sure that assertion is not performed early. + timer.setTimeout(function () { + assert.equal(called, 1, "observer was called before unload only."); + done(); + }, 0); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-keyboard-utils.js b/tools/addon-sdk-1.12/test/test-keyboard-utils.js new file mode 100644 index 0000000..19981de --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-keyboard-utils.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const utils = require("sdk/keyboard/utils"); +const runtime = require("sdk/system/runtime"); + +const isMac = runtime.OS === "Darwin"; + +exports["test toString"] = function(assert) { + assert.equal(utils.toString({ + key: "B", + modifiers: [ "Shift", "Ctrl" ] + }), "Shift-Ctrl-B", "toString does not normalizes JSON"); + + assert.equal(utils.toString({ + key: "C", + modifiers: [], + }), "C", "Works with objects with empty array of modifiers"); + + assert.equal(utils.toString(Object.create((function Type() {}).prototype, { + key: { value: "d" }, + modifiers: { value: [ "alt" ] }, + method: { value: function() {} } + })), "alt-d", "Works with non-json objects"); + + assert.equal(utils.toString({ + modifiers: [ "shift", "alt" ] + }), "shift-alt-", "works with only modifiers"); +}; + +exports["test toJSON"] = function(assert) { + assert.deepEqual(utils.toJSON("Shift-Ctrl-B"), { + key: "b", + modifiers: [ "control", "shift" ] + }, "toJSON normalizes input"); + + assert.deepEqual(utils.toJSON("Meta-Alt-option-C"), { + key: "c", + modifiers: [ "alt", "meta" ] + }, "removes dublicates"); + + assert.deepEqual(utils.toJSON("AccEl+sHiFt+Z", "+"), { + key: "z", + modifiers: isMac ? [ "meta", "shift" ] : [ "control", "shift" ] + }, "normalizes OS specific keys and adjustes seperator"); +}; + +exports["test normalize"] = function assert(assert) { + assert.equal(utils.normalize("Shift Ctrl A control ctrl", " "), + "control shift a", "removes reapeted modifiers"); + assert.equal(utils.normalize("shift-ctrl-left"), "control-shift-left", + "normilizes non printed characters"); + + assert.throws(function() { + utils.normalize("shift-alt-b-z"); + }, "throws if contains more then on non-modifier key"); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-l10n-locale.js b/tools/addon-sdk-1.12/test/test-l10n-locale.js new file mode 100644 index 0000000..19e716d --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-l10n-locale.js @@ -0,0 +1,141 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { getPreferedLocales, findClosestLocale } = require("sdk/l10n/locale"); +const prefs = require("sdk/preferences/service"); +const { Cc, Ci, Cu } = require("chrome"); +const { Services } = Cu.import("resource://gre/modules/Services.jsm"); +const BundleService = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService); + +const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; +const PREF_SELECTED_LOCALE = "general.useragent.locale"; +const PREF_ACCEPT_LANGUAGES = "intl.accept_languages"; + +function assertPrefered(test, expected, msg) { + test.assertEqual(JSON.stringify(getPreferedLocales()), JSON.stringify(expected), + msg); +} + +exports.testGetPreferedLocales = function(test) { + prefs.set(PREF_MATCH_OS_LOCALE, false); + prefs.set(PREF_SELECTED_LOCALE, ""); + prefs.set(PREF_ACCEPT_LANGUAGES, ""); + assertPrefered(test, ["en-us"], + "When all preferences are empty, we only have en-us"); + + prefs.set(PREF_SELECTED_LOCALE, "fr"); + prefs.set(PREF_ACCEPT_LANGUAGES, "jp"); + assertPrefered(test, ["fr", "jp", "en-us"], + "We first have useragent locale, then web one and finally en-US"); + + prefs.set(PREF_SELECTED_LOCALE, "en-US"); + prefs.set(PREF_ACCEPT_LANGUAGES, "en-US"); + assertPrefered(test, ["en-us"], + "We do not have duplicates"); + + prefs.set(PREF_SELECTED_LOCALE, "en-US"); + prefs.set(PREF_ACCEPT_LANGUAGES, "fr"); + assertPrefered(test, ["en-us", "fr"], + "en-US can be first if specified by higher priority preference"); + + // Reset what we changed + prefs.reset(PREF_MATCH_OS_LOCALE); + prefs.reset(PREF_SELECTED_LOCALE); + prefs.reset(PREF_ACCEPT_LANGUAGES); +} + +// In some cases, mainly on Fennec and on Linux version, +// `general.useragent.locale` is a special 'localized' value, like: +// "chrome://global/locale/intl.properties" +exports.testPreferedLocalizedLocale = function(test) { + prefs.set(PREF_MATCH_OS_LOCALE, false); + let bundleURL = "chrome://global/locale/intl.properties"; + prefs.setLocalized(PREF_SELECTED_LOCALE, bundleURL); + let contentLocale = "ja"; + prefs.set(PREF_ACCEPT_LANGUAGES, contentLocale); + + // Read manually the expected locale value from the property file + let expectedLocale = BundleService.createBundle(bundleURL). + GetStringFromName(PREF_SELECTED_LOCALE). + toLowerCase(); + + // First add the useragent locale + let expectedLocaleList = [expectedLocale]; + + // Then the content locale + if (expectedLocaleList.indexOf(contentLocale) == -1) + expectedLocaleList.push(contentLocale); + + // Add default "en-us" fallback if the main language is not already en-us + if (expectedLocaleList.indexOf("en-us") == -1) + expectedLocaleList.push("en-us"); + + assertPrefered(test, expectedLocaleList, "test localized pref value"); + + // Reset what we have changed + prefs.reset(PREF_MATCH_OS_LOCALE); + prefs.reset(PREF_SELECTED_LOCALE); + prefs.reset(PREF_ACCEPT_LANGUAGES); +} + +exports.testPreferedOsLocale = function(test) { + prefs.set(PREF_MATCH_OS_LOCALE, true); + prefs.set(PREF_SELECTED_LOCALE, ""); + prefs.set(PREF_ACCEPT_LANGUAGES, ""); + + let expectedLocale = Services.locale.getLocaleComponentForUserAgent(). + toLowerCase(); + let expectedLocaleList = [expectedLocale]; + + // Add default "en-us" fallback if the main language is not already en-us + if (expectedLocale != "en-us") + expectedLocaleList.push("en-us"); + + assertPrefered(test, expectedLocaleList, "Ensure that we select OS locale when related preference is set"); + + // Reset what we have changed + prefs.reset(PREF_MATCH_OS_LOCALE); + prefs.reset(PREF_SELECTED_LOCALE); + prefs.reset(PREF_ACCEPT_LANGUAGES); +} + +exports.testFindClosestLocale = function(test) { + // Second param of findClosestLocale (aMatchLocales) have to be in lowercase + test.assertEqual(findClosestLocale([], []), null, + "When everything is empty we get null"); + + test.assertEqual(findClosestLocale(["en", "en-US"], ["en"]), + "en", "We always accept exact match first 1/5"); + test.assertEqual(findClosestLocale(["en-US", "en"], ["en"]), + "en", "We always accept exact match first 2/5"); + test.assertEqual(findClosestLocale(["en", "en-US"], ["en-us"]), + "en-US", "We always accept exact match first 3/5"); + test.assertEqual(findClosestLocale(["ja-JP-mac", "ja", "ja-JP"], ["ja-jp"]), + "ja-JP", "We always accept exact match first 4/5"); + test.assertEqual(findClosestLocale(["ja-JP-mac", "ja", "ja-JP"], ["ja-jp-mac"]), + "ja-JP-mac", "We always accept exact match first 5/5"); + + test.assertEqual(findClosestLocale(["en", "en-GB"], ["en-us"]), + "en", "We accept more generic locale, when there is no exact match 1/2"); + test.assertEqual(findClosestLocale(["en-ZA", "en"], ["en-gb"]), + "en", "We accept more generic locale, when there is no exact match 2/2"); + + test.assertEqual(findClosestLocale(["ja-JP"], ["ja"]), + "ja-JP", "We accept more specialized locale, when there is no exact match 1/2"); + // Better to select "ja" in this case but behave same as current AddonManager + test.assertEqual(findClosestLocale(["ja-JP-mac", "ja"], ["ja-jp"]), + "ja-JP-mac", "We accept more specialized locale, when there is no exact match 2/2"); + + test.assertEqual(findClosestLocale(["en-US"], ["en-us"]), + "en-US", "We keep the original one as result 1/2"); + test.assertEqual(findClosestLocale(["en-us"], ["en-us"]), + "en-us", "We keep the original one as result 2/2"); + + test.assertEqual(findClosestLocale(["ja-JP-mac"], ["ja-jp-mac"]), + "ja-JP-mac", "We accept locale with 3 parts"); + test.assertEqual(findClosestLocale(["ja-JP"], ["ja-jp-mac"]), + "ja-JP", "We accept locale with 2 parts from locale with 3 parts"); + test.assertEqual(findClosestLocale(["ja"], ["ja-jp-mac"]), + "ja", "We accept locale with 1 part from locale with 3 parts"); +} diff --git a/tools/addon-sdk-1.12/test/test-l10n-plural-rules.js b/tools/addon-sdk-1.12/test/test-l10n-plural-rules.js new file mode 100644 index 0000000..6ee8a13 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-l10n-plural-rules.js @@ -0,0 +1,82 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { getRulesForLocale } = require("sdk/l10n/plural-rules"); + +// For more information, please visit unicode website: +// http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + +function map(test, f, n, form) { + test.assertEqual(f(n), form, n + " maps to '" + form + "'"); +} + +exports.testFrench = function(test) { + let f = getRulesForLocale("fr"); + map(test, f, -1, "other"); + map(test, f, 0, "one"); + map(test, f, 1, "one"); + map(test, f, 1.5, "one"); + map(test, f, 2, "other"); + map(test, f, 100, "other"); +} + +exports.testEnglish = function(test) { + let f = getRulesForLocale("en"); + map(test, f, -1, "other"); + map(test, f, 0, "other"); + map(test, f, 1, "one"); + map(test, f, 1.5, "other"); + map(test, f, 2, "other"); + map(test, f, 100, "other"); +} + +exports.testArabic = function(test) { + let f = getRulesForLocale("ar"); + map(test, f, -1, "other"); + map(test, f, 0, "zero"); + map(test, f, 0.5, "other"); + + map(test, f, 1, "one"); + map(test, f, 1.5, "other"); + + map(test, f, 2, "two"); + map(test, f, 2.5, "other"); + + map(test, f, 3, "few"); + map(test, f, 3.5, "few"); // I'd expect it to be 'other', but the unicode.org + // algorithm computes 'few'. + map(test, f, 5, "few"); + map(test, f, 10, "few"); + map(test, f, 103, "few"); + map(test, f, 105, "few"); + map(test, f, 110, "few"); + map(test, f, 203, "few"); + map(test, f, 205, "few"); + map(test, f, 210, "few"); + + map(test, f, 11, "many"); + map(test, f, 50, "many"); + map(test, f, 99, "many"); + map(test, f, 111, "many"); + map(test, f, 150, "many"); + map(test, f, 199, "many"); + + map(test, f, 100, "other"); + map(test, f, 101, "other"); + map(test, f, 102, "other"); + map(test, f, 200, "other"); + map(test, f, 201, "other"); + map(test, f, 202, "other"); +} + +exports.testJapanese = function(test) { + // Japanese doesn't have plural forms. + let f = getRulesForLocale("ja"); + map(test, f, -1, "other"); + map(test, f, 0, "other"); + map(test, f, 1, "other"); + map(test, f, 1.5, "other"); + map(test, f, 2, "other"); + map(test, f, 100, "other"); +} diff --git a/tools/addon-sdk-1.12/test/test-layout-change.js b/tools/addon-sdk-1.12/test/test-layout-change.js new file mode 100644 index 0000000..a262251 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-layout-change.js @@ -0,0 +1,178 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// This test makes sure that require statements used by all AMO hosted +// add-ons will be able to use old require statements. +// Tests are based on following usage data: +// https://docs.google.com/spreadsheet/ccc?key=0ApEBy-GRnGxzdHlRMHJ5RXN1aWJ4RGhINkxSd0FCQXc#gid=0 + +exports["test compatibility"] = function(assert) { + assert.equal(require("self"), + require("sdk/self"), "sdk/self -> self"); + + assert.equal(require("tabs"), + require("sdk/tabs"), "sdk/tabs -> tabs"); + + assert.equal(require("widget"), + require("sdk/widget"), "sdk/widget -> widget"); + + assert.equal(require("page-mod"), + require("sdk/page-mod"), "sdk/page-mod -> page-mod"); + + assert.equal(require("panel"), + require("sdk/panel"), "sdk/panel -> panel"); + + assert.equal(require("request"), + require("sdk/request"), "sdk/request -> request"); + + assert.equal(require("chrome"), + require("chrome"), "chrome -> chrome"); + + assert.equal(require("simple-storage"), + require("sdk/simple-storage"), "sdk/simple-storage -> simple-storage"); + + assert.equal(require("context-menu"), + require("sdk/context-menu"), "sdk/context-menu -> context-menu"); + + assert.equal(require("notifications"), + require("sdk/notifications"), "sdk/notifications -> notifications"); + + assert.equal(require("preferences-service"), + require("sdk/preferences/service"), "sdk/preferences/service -> preferences-service"); + + assert.equal(require("window-utils"), + require("sdk/deprecated/window-utils"), "sdk/deprecated/window-utils -> window-utils"); + + assert.equal(require("url"), + require("sdk/url"), "sdk/url -> url"); + + assert.equal(require("selection"), + require("sdk/selection"), "sdk/selection -> selection"); + + assert.equal(require("timers"), + require("sdk/timers"), "sdk/timers -> timers"); + + assert.equal(require("simple-prefs"), + require("sdk/simple-prefs"), "sdk/simple-prefs -> simple-prefs"); + + assert.equal(require("traceback"), + require("sdk/console/traceback"), "sdk/console/traceback -> traceback"); + + assert.equal(require("unload"), + require("sdk/system/unload"), "sdk/system/unload -> unload"); + + assert.equal(require("hotkeys"), + require("sdk/hotkeys"), "sdk/hotkeys -> hotkeys"); + + assert.equal(require("clipboard"), + require("sdk/clipboard"), "sdk/clipboard -> clipboard"); + + assert.equal(require("windows"), + require("sdk/windows"), "sdk/windows -> windows"); + + assert.equal(require("page-worker"), + require("sdk/page-worker"), "sdk/page-worker -> page-worker"); + + assert.equal(require("timer"), + require("sdk/timers"), "sdk/timers -> timer"); + + assert.equal(require("xhr"), + require("sdk/net/xhr"), "sdk/io/xhr -> xhr"); + + assert.equal(require("observer-service"), + require("sdk/deprecated/observer-service"), "sdk/deprecated/observer-service -> observer-service"); + + assert.equal(require("private-browsing"), + require("sdk/private-browsing"), "sdk/private-browsing -> private-browsing"); + + assert.equal(require("passwords"), + require("sdk/passwords"), "sdk/passwords -> passwords"); + + assert.equal(require("events"), + require("sdk/deprecated/events"), "sdk/deprecated/events -> events"); + + assert.equal(require("match-pattern"), + require("sdk/page-mod/match-pattern"), "sdk/page-mod/match-pattern -> match-pattern"); + + assert.equal(require("tab-browser"), + require("sdk/deprecated/tab-browser"), "sdk/deprecated/tab-browser -> tab-browser"); + + assert.equal(require("file"), + require("sdk/io/file"), "sdk/io/file -> file"); + + assert.equal(require("xul-app"), + require("sdk/system/xul-app"), "sdk/system/xul-app -> xul-app"); + + assert.equal(require("api-utils"), + require("sdk/deprecated/api-utils"), "sdk/deprecated/api-utils -> api-utils"); + + assert.equal(require("runtime"), + require("sdk/system/runtime"), "sdk/system/runtime -> runtime"); + + assert.equal(require("base64"), + require("sdk/base64"), "sdk/base64 -> base64"); + + assert.equal(require("xpcom"), + require("sdk/platform/xpcom"), "sdk/platform/xpcom -> xpcom"); + + assert.equal(require("traits"), + require("sdk/deprecated/traits"), "sdk/deprecated/traits -> traits"); + + assert.equal(require("keyboard/utils"), + require("sdk/keyboard/utils"), "sdk/keyboard/utils -> keyboard/utils"); + + assert.equal(require("system"), + require("sdk/system"), "sdk/system -> system"); + + assert.equal(require("querystring"), + require("sdk/querystring"), "sdk/querystring -> querystring"); + + assert.equal(require("addon-page"), + require("sdk/addon-page"), "sdk/addon-page -> addon-page"); + + assert.equal(require("tabs/utils"), + require("sdk/tabs/utils"), "sdk/tabs/utils -> tabs/utils"); + + assert.equal(require("app-strings"), + require("sdk/deprecated/app-strings"), "sdk/deprecated/app-strings -> app-strings"); + + assert.equal(require("dom/events"), + require("sdk/dom/events"), "sdk/dom/events -> dom/events"); + + assert.equal(require("tabs/tab.js"), + require("sdk/tabs/tab"), "sdk/tabs/tab -> tabs/tab.js"); + + assert.equal(require("memory"), + require("sdk/deprecated/memory"), "sdk/deprecated/memory -> memory"); + + assert.equal(require("light-traits"), + require("sdk/deprecated/light-traits"), "sdk/deprecated/light-traits -> light-traits"); + + assert.equal(require("environment"), + require("sdk/system/environment"), "sdk/system/environment -> environment"); + + assert.equal(require("utils/data"), + require("sdk/io/data"), "sdk/io/data -> utils/data"); + + assert.equal(require("test/assert"), + require("sdk/test/assert"), "sdk/test/assert -> test/assert"); + + assert.equal(require("hidden-frame"), + require("sdk/frame/hidden-frame"), "sdk/frame/hidden-frame -> hidden-frame"); + + assert.equal(require("collection"), + require("sdk/util/collection"), "sdk/util/collection -> collection"); + + assert.equal(require("array"), + require("sdk/util/array"), "sdk/util/array -> array"); + + assert.equal(require("api-utils/cortex"), + require("sdk/deprecated/cortex"), + "api-utils/cortex -> sdk/deprecated/cortex"); +}; + + +require('test').run(exports); diff --git a/tools/addon-sdk-1.12/test/test-light-traits.js b/tools/addon-sdk-1.12/test/test-light-traits.js new file mode 100644 index 0000000..7c5ca42 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-light-traits.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +exports["test traits from objects"] = require("./traits/object-tests"); +exports["test traits from descriptors"] = require("./traits/descriptor-tests"); +exports["test inheritance"] = require("./traits/inheritance-tests"); + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-list.js b/tools/addon-sdk-1.12/test/test-list.js new file mode 100644 index 0000000..1e345d2 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-list.js @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { List, addListItem, removeListItem } = require('sdk/util/list'); +const { Class } = require('sdk/core/heritage'); + +exports.testList = function(test) { + let list = List(); + addListItem(list, 1); + + for (let key in list) { + test.assertEqual(key, 0, 'key is correct'); + test.assertEqual(list[key], 1, 'value is correct'); + } + + let count = 0; + for each (let ele in list) { + test.assertEqual(ele, 1, 'ele is correct'); + test.assertEqual(++count, 1, 'count is correct'); + } + + removeListItem(list, 1); + test.assertEqual(list.length, 0, 'remove worked'); +}; + +exports.testImplementsList = function(test) { + let List2 = Class({ + implements: [List], + initialize: function() { + List.prototype.initialize.apply(this, [0, 1, 2]); + } + }); + let list2 = List2(); + let count = 0; + for each (let ele in list2) { + test.assertEqual(ele, count++, 'ele is correct'); + } + addListItem(list2, 3); + test.assertEqual(list2.length, 4, '3 was added'); + test.assertEqual(list2[list2.length-1], 3, '3 was added'); +} diff --git a/tools/addon-sdk-1.12/test/test-loader.js b/tools/addon-sdk-1.12/test/test-loader.js new file mode 100644 index 0000000..08c285f --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-loader.js @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +let { Loader, main, unload } = require('toolkit/loader'); + +exports['test dependency cycles'] = function(assert) { + let uri = module.uri.substr(0, module.uri.lastIndexOf('/')) + + '/fixtures/loader/cycles/' + + let loader = Loader({ + paths: { '': uri } + }); + + let program = main(loader, 'main') + + assert.equal(program.a.b, program.b, 'module `a` gets correct `b`') + assert.equal(program.b.a, program.a, 'module `b` gets correct `a`') + assert.equal(program.c.main, program, 'module `c` gets correct `main`') + + unload(loader); +}; + +require('test').run(exports); + diff --git a/tools/addon-sdk-1.12/test/test-match-pattern.js b/tools/addon-sdk-1.12/test/test-match-pattern.js new file mode 100644 index 0000000..60cdac2 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-match-pattern.js @@ -0,0 +1,129 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const { MatchPattern } = require("sdk/page-mod/match-pattern"); + +exports.testMatchPatternTestTrue = function(test) { + function ok(pattern, url) { + let mp = new MatchPattern(pattern); + test.assert(mp.test(url), pattern + " should match " + url); + } + + ok("*", "http://example.com"); + ok("*", "https://example.com"); + ok("*", "ftp://example.com"); + + ok("*.example.com", "http://example.com"); + ok("*.example.com", "http://hamburger.example.com"); + ok("*.example.com", "http://hotdog.hamburger.example.com"); + + ok("http://example.com*", "http://example.com"); + ok("http://example.com*", "http://example.com/"); + ok("http://example.com/*", "http://example.com/"); + ok("http://example.com/*", "http://example.com/potato-salad"); + ok("http://example.com/pickles/*", "http://example.com/pickles/"); + ok("http://example.com/pickles/*", "http://example.com/pickles/lemonade"); + + ok("http://example.com", "http://example.com"); + ok("http://example.com/ice-cream", "http://example.com/ice-cream"); + + ok(/.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753"); + ok(/https:.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753"); +}; + +exports.testMatchPatternTestFalse = function(test) { + function ok(pattern, url) { + let mp = new MatchPattern(pattern); + test.assert(!mp.test(url), pattern + " should not match " + url); + } + + ok("*", null); + ok("*", ""); + ok("*", "bogus"); + ok("*", "chrome://browser/content/browser.xul"); + ok("*", "nttp://example.com"); + + ok("*.example.com", null); + ok("*.example.com", ""); + ok("*.example.com", "bogus"); + ok("*.example.com", "http://example.net"); + ok("*.example.com", "http://foo.com"); + ok("*.example.com", "http://example.com.foo"); + ok("*.example2.com", "http://example.com"); + + ok("http://example.com/*", null); + ok("http://example.com/*", ""); + ok("http://example.com/*", "bogus"); + ok("http://example.com/*", "http://example.com"); + ok("http://example.com/*", "http://foo.com/"); + + ok("http://example.com", null); + ok("http://example.com", ""); + ok("http://example.com", "bogus"); + ok("http://example.com", "http://example.com/"); + + ok(/zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753"); + ok(/.*zilla/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753"); + ok(/.*Zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=655464"); // bug 655464 + ok(/https:.*zilla/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753"); +}; + +exports.testMatchPatternErrors = function(test) { + test.assertRaises( + function() new MatchPattern("*.google.com/*"), + /There can be at most one/, + "MatchPattern throws when supplied multiple '*'" + ); + + test.assertRaises( + function() new MatchPattern("google.com"), + /expected to be either an exact URL/, + "MatchPattern throws when the wildcard doesn't use '*' and doesn't " + + "look like a URL" + ); + + test.assertRaises( + function() new MatchPattern("http://google*.com"), + /expected to be the first or the last/, + "MatchPattern throws when a '*' is in the middle of the wildcard" + ); + + test.assertRaises( + function() new MatchPattern(/ /g), + /^A RegExp match pattern cannot be set to `global` \(i\.e\. \/\/g\)\.$/, + "MatchPattern throws on a RegExp set to `global` (i.e. //g)." + ); + + test.assertRaises( + function() new MatchPattern(/ /i), + /^A RegExp match pattern cannot be set to `ignoreCase` \(i\.e\. \/\/i\)\.$/, + "MatchPattern throws on a RegExp set to `ignoreCase` (i.e. //i)." + ); + + test.assertRaises( + function() new MatchPattern( / /m ), + /^A RegExp match pattern cannot be set to `multiline` \(i\.e\. \/\/m\)\.$/, + "MatchPattern throws on a RegExp set to `multiline` (i.e. //m)." + ); +}; + +exports.testMatchPatternInternals = function(test) { + test.assertEqual( + new MatchPattern("http://google.com/test").exactURL, + "http://google.com/test" + ); + + test.assertEqual( + new MatchPattern("http://google.com/test/*").urlPrefix, + "http://google.com/test/" + ); + + test.assertEqual( + new MatchPattern("*.example.com").domain, + "example.com" + ); +}; diff --git a/tools/addon-sdk-1.12/test/test-memory.js b/tools/addon-sdk-1.12/test/test-memory.js new file mode 100644 index 0000000..e225707 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-memory.js @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var memory = require("sdk/deprecated/memory"); + +exports.testMemory = function(test) { + test.pass("Skipping this test until Gecko memory debugging issues " + + "are resolved (see bug 592774)."); + return; + + var obj = {}; + memory.track(obj, "testMemory.testObj"); + var objs = memory.getObjects("testMemory.testObj"); + test.assertEqual(objs[0].weakref.get(), obj); + obj = null; + memory.gc(); + test.assertEqual(objs[0].weakref.get(), null); +}; diff --git a/tools/addon-sdk-1.12/test/test-module.js b/tools/addon-sdk-1.12/test/test-module.js new file mode 100644 index 0000000..6c9c6c2 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-module.js @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** Disabled because of Bug 672199 +exports["test module exports are frozen"] = function(assert) { + assert.ok(Object.isFrozen(require("sdk/hotkeys")), + "module exports are frozen"); +}; + +exports["test redefine exported property"] = function(assert) { + let hotkeys = require("sdk/hotkeys"); + let { Hotkey } = hotkeys; + try { Object.defineProperty(hotkeys, 'Hotkey', { value: {} }); } catch(e) {} + assert.equal(hotkeys.Hotkey, Hotkey, "exports can't be redefined"); +}; +*/ + +exports["test can't delete exported property"] = function(assert) { + let hotkeys = require("sdk/hotkeys"); + let { Hotkey } = hotkeys; + + try { delete hotkeys.Hotkey; } catch(e) {} + assert.equal(hotkeys.Hotkey, Hotkey, "exports can't be deleted"); +}; + +exports["test can't override exported property"] = function(assert) { + let hotkeys = require("sdk/hotkeys"); + let { Hotkey } = hotkeys; + + try { hotkeys.Hotkey = Object } catch(e) {} + assert.equal(hotkeys.Hotkey, Hotkey, "exports can't be overriden"); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-modules.js b/tools/addon-sdk-1.12/test/test-modules.js new file mode 100644 index 0000000..2868560 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-modules.js @@ -0,0 +1,148 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.testDefine = function(test) { + let tiger = require('./modules/tiger'); + test.assertEqual(tiger.name, 'tiger', 'name proprety was exported properly'); + test.assertEqual(tiger.type, 'cat', 'property form other module exported'); +}; + +exports.testDefineInoresNonFactory = function(test) { + let mod = require('./modules/async2'); + test.assertEqual(mod.name, 'async2', 'name proprety was exported properly'); + test.assertNotEqual(mod.traditional2Name, 'traditional2', '1st is ignored'); +}; +/* Disable test that require AMD specific functionality: + +// define() that exports a function as the module value, +// specifying a module name. +exports.testDefExport = function(test) { + var add = require('modules/add'); + test.assertEqual(add(1, 1), 2, 'Named define() exporting a function'); +}; + +// define() that exports function as a value, but is anonymous +exports.testAnonDefExport = function (test) { + var subtract = require('modules/subtract'); + test.assertEqual(subtract(4, 2), 2, + 'Anonymous define() exporting a function'); +} + +// using require([], function () {}) to load modules. +exports.testSimpleRequire = function (test) { + require(['modules/blue', 'modules/orange'], function (blue, orange) { + test.assertEqual(blue.name, 'blue', 'Simple require for blue'); + test.assertEqual(orange.name, 'orange', 'Simple require for orange'); + test.assertEqual(orange.parentType, 'color', + 'Simple require dependency check for orange'); + }); +} + +// using nested require([]) calls. +exports.testSimpleRequireNested = function (test) { + require(['modules/blue', 'modules/orange', 'modules/green'], + function (blue, orange, green) { + + require(['modules/orange', 'modules/red'], function (orange, red) { + test.assertEqual(red.name, 'red', 'Simple require for red'); + test.assertEqual(red.parentType, 'color', + 'Simple require dependency check for red'); + test.assertEqual(blue.name, 'blue', 'Simple require for blue'); + test.assertEqual(orange.name, 'orange', 'Simple require for orange'); + test.assertEqual(orange.parentType, 'color', + 'Simple require dependency check for orange'); + test.assertEqual(green.name, 'green', 'Simple require for green'); + test.assertEqual(green.parentType, 'color', + 'Simple require dependency check for green'); + }); + + }); +} + +// requiring a traditional module, that uses async, that use traditional and +// async, with a circular reference +exports.testMixedCircular = function (test) { + var t = require('modules/traditional1'); + test.assertEqual(t.name, 'traditional1', 'Testing name'); + test.assertEqual(t.traditional2Name, 'traditional2', + 'Testing dependent name'); + test.assertEqual(t.traditional1Name, 'traditional1', 'Testing circular name'); + test.assertEqual(t.async2Name, 'async2', 'Testing async2 name'); + test.assertEqual(t.async2Traditional2Name, 'traditional2', + 'Testing nested traditional2 name'); +} + +// Testing define()(function(require) {}) with some that use exports, +// some that use return. +exports.testAnonExportsReturn = function (test) { + var lion = require('modules/lion'); + require(['modules/tiger', 'modules/cheetah'], function (tiger, cheetah) { + test.assertEqual('lion', lion, 'Check lion name'); + test.assertEqual('tiger', tiger.name, 'Check tiger name'); + test.assertEqual('cat', tiger.type, 'Check tiger type'); + test.assertEqual('cheetah', cheetah(), 'Check cheetah name'); + }); +} + +// circular dependency +exports.testCircular = function (test) { + var pollux = require('modules/pollux'), + castor = require('modules/castor'); + + test.assertEqual(pollux.name, 'pollux', 'Pollux\'s name'); + test.assertEqual(pollux.getCastorName(), + 'castor', 'Castor\'s name from Pollux.'); + test.assertEqual(castor.name, 'castor', 'Castor\'s name'); + test.assertEqual(castor.getPolluxName(), 'pollux', + 'Pollux\'s name from Castor.'); +} + +// test a bad module that asks for exports but also does a define() return +exports.testBadExportAndReturn = function (test) { + var passed = false; + try { + var bad = require('modules/badExportAndReturn'); + } catch(e) { + passed = /cannot use exports and also return/.test(e.toString()); + } + test.assertEqual(passed, true, 'Make sure exports and return fail'); +} + +// test a bad circular dependency, where an exported value is needed, but +// the return value happens too late, a module already asked for the exported +// value. +exports.testBadExportAndReturnCircular = function (test) { + var passed = false; + try { + var bad = require('modules/badFirst'); + } catch(e) { + passed = /after another module has referenced its exported value/ + .test(e.toString()); + } + test.assertEqual(passed, true, 'Make sure return after an exported ' + + 'value is grabbed by another module fails.'); +} + +// only allow one define call per file. +exports.testOneDefine = function (test) { + var passed = false; + try { + var dupe = require('modules/dupe'); + } catch(e) { + passed = /Only one call to define/.test(e.toString()); + } + test.assertEqual(passed, true, 'Only allow one define call per module'); +} + +// only allow one define call per file, testing a bad nested define call. +exports.testOneDefineNested = function (test) { + var passed = false; + try { + var dupe = require('modules/dupeNested'); + } catch(e) { + passed = /Only one call to define/.test(e.toString()); + } + test.assertEqual(passed, true, 'Only allow one define call per module'); +} +*/ diff --git a/tools/addon-sdk-1.12/test/test-namespace.js b/tools/addon-sdk-1.12/test/test-namespace.js new file mode 100644 index 0000000..dfe88da --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-namespace.js @@ -0,0 +1,122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { ns } = require("sdk/core/namespace"); +const { Cc, Ci, Cu } = require("chrome"); +const { setTimeout } = require("sdk/timers") + +exports["test post GC references"] = function (assert, done) { + var target = {}, local = ns() + local(target).there = true + + assert.equal(local(target).there, true, "namespaced preserved"); + + setTimeout(function() { + Cu.forceGC(); + assert.equal(local(target).there, true, "namespace is preserved post GC"); + done(); + }, 300); +}; + +exports["test namsepace basics"] = function(assert) { + var privates = ns(); + var object = { foo: function foo() { return "hello foo"; } }; + + assert.notEqual(privates(object), object, + "namespaced object is not the same"); + assert.ok(!('foo' in privates(object)), + "public properties are not in the namespace"); + + assert.equal(privates(object), privates(object), + "same namespaced object is returned on each call"); +}; + +exports["test namespace overlays"] = function(assert) { + var _ = ns(); + var object = { foo: 'foo' }; + + _(object).foo = 'bar'; + + assert.equal(_(object).foo, "bar", + "namespaced property `foo` changed value"); + + assert.equal(object.foo, "foo", + "public property `foo` has original value"); + + object.foo = "baz"; + assert.equal(_(object).foo, "bar", + "property changes do not affect namespaced properties"); + + object.bar = "foo"; + assert.ok(!("bar" in _(object)), + "new public properties are not reflected in namespace"); +}; + +exports["test shared namespaces"] = function(assert) { + var _ = ns(); + + var f1 = { hello: 1 }; + var f2 = { foo: 'foo', hello: 2 }; + _(f1).foo = _(f2).foo = 'bar'; + + assert.equal(_(f1).hello, _(f2).hello, "namespace can be shared"); + assert.notEqual(f1.hello, _(f1).hello, "shared namespace can overlay"); + assert.notEqual(f2.hello, _(f2).hello, "target is not affected"); + + _(f1).hello = 3; + + assert.notEqual(_(f1).hello, _(f2).hello, + "namespaced property can be overided"); + assert.equal(_(f2).hello, _({}).hello, "namespace does not change"); +}; + +exports["test multi namespace"] = function(assert) { + var n1 = ns(); + var n2 = ns(); + var object = { baz: 1 }; + n1(object).foo = 1; + n2(object).foo = 2; + n1(object).bar = n2(object).bar = 3; + + assert.notEqual(n1(object).foo, n2(object).foo, + "object can have multiple namespaces"); + assert.equal(n1(object).bar, n2(object).bar, + "object can have matching props in diff namespaces"); +}; + +exports["test ns alias"] = function(assert) { + assert.strictEqual(ns, require('sdk/core/namespace').Namespace, + "ns is an alias of Namespace"); +}; + +exports["test ns inheritance"] = function(assert) { + let _ = ns(); + + let prototype = { level: 1 }; + let object = Object.create(prototype); + let delegee = Object.create(object); + + _(prototype).foo = {}; + + assert.ok(!Object.prototype.hasOwnProperty.call(_(delegee), "foo"), + "namespaced property is not copied to descendants"); + assert.equal(_(delegee).foo, _(prototype).foo, + "namespaced properties are inherited by descendants"); + + _(object).foo = {}; + assert.notEqual(_(object).foo, _(prototype).foo, + "namespaced properties may be shadowed"); + assert.equal(_(object).foo, _(delegee).foo, + "shadwed properties are inherited by descendants"); + + _(object).bar = {}; + assert.ok(!("bar" in _(prototype)), + "descendants properties are not copied to ancestors"); + assert.ok(_(object).bar, _(delegee).bar, + "descendants properties are inherited"); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-net-url.js b/tools/addon-sdk-1.12/test/test-net-url.js new file mode 100644 index 0000000..302c402 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-net-url.js @@ -0,0 +1,212 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { readURI, readURISync } = require("sdk/net/url"); +const { data } = require("self"); + +const utf8text = "Hello, ゼロ!"; +const latin1text = "Hello, ゼãƒ!"; + +const dataURIutf8 = "data:text/plain;charset=utf-8," + encodeURIComponent(utf8text); +const dataURIlatin1 = "data:text/plain;charset=ISO-8859-1," + escape(latin1text); +const chromeURI = "chrome://global-platform/locale/accessible.properties"; + +exports["test async readURI"] = function(assert, done) { + let content = ""; + + readURI(data.url("test-net-url.txt")).then(function(data) { + content = data; + assert.equal(content, utf8text, "The URL content is loaded properly"); + done(); + }, function() { + assert.fail("should not reject"); + done(); + }) + + assert.equal(content, "", "The URL content is not load yet"); +} + +exports["test sync readURI"] = function(assert) { + let content = ""; + + readURI(data.url("test-net-url.txt"), { sync: true }).then(function(data) { + content = data; + }, function() { + assert.fail("should not reject"); + }) + + assert.equal(content, utf8text, "The URL content is loaded properly"); +} + +exports["test readURISync"] = function(assert) { + let content = readURISync(data.url("test-net-url.txt")); + + assert.equal(content, utf8text, "The URL content is loaded properly"); +} + +exports["test async readURI with ISO-8859-1 charset"] = function(assert, done) { + let content = ""; + + readURI(data.url("test-net-url.txt"), { charset : "ISO-8859-1"}).then(function(data) { + content = data; + assert.equal(content, latin1text, "The URL content is loaded properly"); + done(); + }, function() { + assert.fail("should not reject"); + done(); + }) + + assert.equal(content, "", "The URL content is not load yet"); +} + +exports["test sync readURI with ISO-8859-1 charset"] = function(assert) { + let content = ""; + + readURI(data.url("test-net-url.txt"), { + sync: true, + charset: "ISO-8859-1" + }).then(function(data) { + content = data; + }, function() { + assert.fail("should not reject"); + }) + + assert.equal(content, latin1text, "The URL content is loaded properly"); +} + +exports["test readURISync with ISO-8859-1 charset"] = function(assert) { + let content = readURISync(data.url("test-net-url.txt"), "ISO-8859-1"); + + assert.equal(content, latin1text, "The URL content is loaded properly"); +} + +exports["test async readURI with not existing file"] = function(assert, done) { + readURI(data.url("test-net-url-fake.txt")).then(function(data) { + assert.fail("should not resolve"); + done(); + }, function(reason) { + assert.ok(reason.indexOf("Failed to read:") === 0); + done(); + }) +} + +exports["test sync readURI with not existing file"] = function(assert) { + readURI(data.url("test-net-url-fake.txt"), { sync: true }).then(function(data) { + assert.fail("should not resolve"); + }, function(reason) { + assert.ok(reason.indexOf("Failed to read:") === 0); + }) +} + +exports["test readURISync with not existing file"] = function(assert) { + assert.throws(function() { + readURISync(data.url("test-net-url-fake.txt")); + }, /NS_ERROR_FILE_NOT_FOUND/); +} + +exports["test async readURI with data URI"] = function(assert, done) { + let content = ""; + + readURI(dataURIutf8).then(function(data) { + content = data; + assert.equal(content, utf8text, "The URL content is loaded properly"); + done(); + }, function() { + assert.fail("should not reject"); + done(); + }) + + assert.equal(content, "", "The URL content is not load yet"); +} + +exports["test sync readURI with data URI"] = function(assert) { + let content = ""; + + readURI(dataURIutf8, { sync: true }).then(function(data) { + content = data; + }, function() { + assert.fail("should not reject"); + }) + + assert.equal(content, utf8text, "The URL content is loaded properly"); +} + +exports["test readURISync with data URI"] = function(assert) { + let content = readURISync(dataURIutf8); + + assert.equal(content, utf8text, "The URL content is loaded properly"); +} + +exports["test async readURI with data URI and ISO-8859-1 charset"] = function(assert, done) { + let content = ""; + + readURI(dataURIlatin1, { charset : "ISO-8859-1"}).then(function(data) { + content = unescape(data); + assert.equal(content, latin1text, "The URL content is loaded properly"); + done(); + }, function() { + assert.fail("should not reject"); + done(); + }) + + assert.equal(content, "", "The URL content is not load yet"); +} + +exports["test sync readURI with data URI and ISO-8859-1 charset"] = function(assert) { + let content = ""; + + readURI(dataURIlatin1, { + sync: true, + charset: "ISO-8859-1" + }).then(function(data) { + content = unescape(data); + }, function() { + assert.fail("should not reject"); + }) + + assert.equal(content, latin1text, "The URL content is loaded properly"); +} + +exports["test readURISync with data URI and ISO-8859-1 charset"] = function(assert) { + let content = unescape(readURISync(dataURIlatin1, "ISO-8859-1")); + + assert.equal(content, latin1text, "The URL content is loaded properly"); +} + +exports["test readURISync with chrome URI"] = function(assert) { + let content = readURISync(chromeURI); + + assert.ok(content, "The URL content is loaded properly"); +} + +exports["test async readURI with chrome URI"] = function(assert, done) { + let content = ""; + + readURI(chromeURI).then(function(data) { + content = data; + assert.equal(content, readURISync(chromeURI), "The URL content is loaded properly"); + done(); + }, function() { + assert.fail("should not reject"); + done(); + }) + + assert.equal(content, "", "The URL content is not load yet"); +} + +exports["test sync readURI with chrome URI"] = function(assert) { + let content = ""; + + readURI(chromeURI, { sync: true }).then(function(data) { + content = data; + }, function() { + assert.fail("should not reject"); + }) + + assert.equal(content, readURISync(chromeURI), "The URL content is loaded properly"); +} + +require("test").run(exports) diff --git a/tools/addon-sdk-1.12/test/test-notifications.js b/tools/addon-sdk-1.12/test/test-notifications.js new file mode 100644 index 0000000..3f29410 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-notifications.js @@ -0,0 +1,46 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim:set ts=2 sw=2 sts=2 et filetype=javascript + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { Loader } = require('sdk/test/loader'); + +exports.testOnClick = function (test) { + let [loader, mockAlertServ] = makeLoader(module); + let notifs = loader.require("sdk/notifications"); + let data = "test data"; + let opts = { + onClick: function (clickedData) { + test.assertEqual(this, notifs, "|this| should be notifications module"); + test.assertEqual(clickedData, data, + "data passed to onClick should be correct"); + }, + data: data, + title: "test title", + text: "test text", + iconURL: "test icon URL" + }; + notifs.notify(opts); + mockAlertServ.click(); + loader.unload(); +}; + +// Returns [loader, mockAlertService]. +function makeLoader(test) { + let loader = Loader(module); + let mockAlertServ = { + showAlertNotification: function (imageUrl, title, text, textClickable, + cookie, alertListener, name) { + this._cookie = cookie; + this._alertListener = alertListener; + }, + click: function () { + this._alertListener.observe(null, "alertclickcallback", this._cookie); + } + }; + loader.require("sdk/notifications"); + let scope = loader.sandbox("sdk/notifications"); + scope.notify = mockAlertServ.showAlertNotification.bind(mockAlertServ); + return [loader, mockAlertServ]; +}; diff --git a/tools/addon-sdk-1.12/test/test-observer-service.js b/tools/addon-sdk-1.12/test/test-observer-service.js new file mode 100644 index 0000000..bd4c903 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-observer-service.js @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const observers = require("sdk/deprecated/observer-service"); +const { Cc, Ci } = require("chrome"); +const { Loader } = require("sdk/test/loader"); +const { PlainTextConsole } = require("sdk/console/plain-text"); + +exports.testUnloadAndErrorLogging = function(test) { + var prints = []; + var loader = Loader(module, { + console: new PlainTextConsole(function(_) { + prints.push(_); + }) + }); + var sbobsvc = loader.require("sdk/deprecated/observer-service"); + + var timesCalled = 0; + var cb = function(subject, data) { + timesCalled++; + }; + var badCb = function(subject, data) { + throw new Error("foo"); + }; + sbobsvc.add("blarg", cb); + observers.notify("blarg", "yo yo"); + test.assertEqual(timesCalled, 1); + sbobsvc.add("narg", badCb); + observers.notify("narg", "yo yo"); + var lines = prints[0].split("\n"); + test.assertEqual(lines[0], "error: " + require("sdk/self").name + ": An exception occurred."); + test.assertEqual(lines[1], "Traceback (most recent call last):"); + test.assertEqual(lines.slice(-2)[0], "Error: foo"); + + loader.unload(); + observers.notify("blarg", "yo yo"); + test.assertEqual(timesCalled, 1); +}; + +exports.testObserverService = function(test) { + var ios = Cc['@mozilla.org/network/io-service;1'] + .getService(Ci.nsIIOService); + var service = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + var uri = ios.newURI("http://www.foo.com", null, null); + var timesCalled = 0; + var lastSubject = null; + var lastData = null; + + var cb = function(subject, data) { + timesCalled++; + lastSubject = subject; + lastData = data; + }; + + observers.add("blarg", cb); + service.notifyObservers(uri, "blarg", "some data"); + test.assertEqual(timesCalled, 1, + "observer-service.add() should call callback"); + test.assertEqual(lastSubject, uri, + "observer-service.add() should pass subject"); + test.assertEqual(lastData, "some data", + "observer-service.add() should pass data"); + + function customSubject() {} + function customData() {} + observers.notify("blarg", customSubject, customData); + test.assertEqual(timesCalled, 2, + "observer-service.notify() should work"); + test.assertEqual(lastSubject, customSubject, + "observer-service.notify() should pass+wrap subject"); + test.assertEqual(lastData, customData, + "observer-service.notify() should pass data"); + + observers.remove("blarg", cb); + service.notifyObservers(null, "blarg", "some data"); + test.assertEqual(timesCalled, 2, + "observer-service.remove() should work"); +}; diff --git a/tools/addon-sdk-1.12/test/test-packaging.js b/tools/addon-sdk-1.12/test/test-packaging.js new file mode 100644 index 0000000..b7a96cd --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-packaging.js @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var url = require("sdk/url"); +var file = require("sdk/io/file"); +var {Cm,Ci} = require("chrome"); +var options = require("@loader/options"); + +exports.testPackaging = function(test) { + + test.assertEqual(options.metadata.description, + "Add-on development made easy.", + "packaging metadata should be available"); +}; diff --git a/tools/addon-sdk-1.12/test/test-page-mod.js b/tools/addon-sdk-1.12/test/test-page-mod.js new file mode 100644 index 0000000..9cb9265 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-page-mod.js @@ -0,0 +1,918 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +var pageMod = require("sdk/page-mod"); +var testPageMod = require("./pagemod-test-helpers").testPageMod; +const { Loader } = require('sdk/test/loader'); +const tabs = require("sdk/tabs"); +const timer = require("sdk/timers"); +const { Cc, Ci } = require("chrome"); +const { open, getFrames, getMostRecentBrowserWindow } = require('sdk/window/utils'); +const windowUtils = require('sdk/deprecated/window-utils'); +const { getTabContentWindow, getActiveTab, openTab, closeTab } = require('sdk/tabs/utils'); +const { data } = require('self'); + +/* XXX This can be used to delay closing the test Firefox instance for interactive + * testing or visual inspection. This test is registered first so that it runs + * the last. */ +exports.delay = function(test) { + if (false) { + test.waitUntilDone(60000); + timer.setTimeout(function() {test.done();}, 4000); + } else + test.pass(); +} + +function Isolate(worker) { + return "(" + worker + ")()"; +} + +/* Tests for the PageMod APIs */ + +exports.testPageMod1 = function(test) { + let mods = testPageMod(test, "about:", [{ + include: /about:/, + contentScriptWhen: 'end', + contentScript: 'new ' + function WorkerScope() { + window.document.body.setAttribute("JEP-107", "worked"); + }, + onAttach: function() { + test.assertEqual(this, mods[0], "The 'this' object is the page mod."); + } + }], + function(win, done) { + test.assertEqual( + win.document.body.getAttribute("JEP-107"), + "worked", + "PageMod.onReady test" + ); + done(); + } + ); +}; + +exports.testPageMod2 = function(test) { + testPageMod(test, "about:", [{ + include: "about:*", + contentScript: [ + 'new ' + function contentScript() { + window.AUQLUE = function() { return 42; } + try { + window.AUQLUE() + } + catch(e) { + throw new Error("PageMod scripts executed in order"); + } + document.documentElement.setAttribute("first", "true"); + }, + 'new ' + function contentScript() { + document.documentElement.setAttribute("second", "true"); + } + ] + }], function(win, done) { + test.assertEqual(win.document.documentElement.getAttribute("first"), + "true", + "PageMod test #2: first script has run"); + test.assertEqual(win.document.documentElement.getAttribute("second"), + "true", + "PageMod test #2: second script has run"); + test.assertEqual("AUQLUE" in win, false, + "PageMod test #2: scripts get a wrapped window"); + done(); + }); +}; + +exports.testPageModIncludes = function(test) { + var asserts = []; + function createPageModTest(include, expectedMatch) { + // Create an 'onload' test function... + asserts.push(function(test, win) { + var matches = include in win.localStorage; + test.assert(expectedMatch ? matches : !matches, + "'" + include + "' match test, expected: " + expectedMatch); + }); + // ...and corresponding PageMod options + return { + include: include, + contentScript: 'new ' + function() { + self.on("message", function(msg) { + window.localStorage[msg] = true; + }); + }, + // The testPageMod callback with test assertions is called on 'end', + // and we want this page mod to be attached before it gets called, + // so we attach it on 'start'. + contentScriptWhen: 'start', + onAttach: function(worker) { + worker.postMessage(this.include[0]); + } + }; + } + + testPageMod(test, "about:buildconfig", [ + createPageModTest("*", false), + createPageModTest("*.google.com", false), + createPageModTest("about:*", true), + createPageModTest("about:", false), + createPageModTest("about:buildconfig", true) + ], + function (win, done) { + test.waitUntil(function () win.localStorage["about:buildconfig"], + "about:buildconfig page-mod to be executed") + .then(function () { + asserts.forEach(function(fn) { + fn(test, win); + }); + done(); + }); + } + ); +}; + +exports.testPageModErrorHandling = function(test) { + test.assertRaises(function() { + new pageMod.PageMod(); + }, + 'pattern is undefined', + "PageMod() throws when 'include' option is not specified."); +}; + +/* Tests for internal functions. */ +exports.testCommunication1 = function(test) { + let workerDone = false, + callbackDone = null; + + testPageMod(test, "about:", [{ + include: "about:*", + contentScriptWhen: 'end', + contentScript: 'new ' + function WorkerScope() { + self.on('message', function(msg) { + document.body.setAttribute('JEP-107', 'worked'); + self.postMessage(document.body.getAttribute('JEP-107')); + }) + }, + onAttach: function(worker) { + worker.on('error', function(e) { + test.fail('Errors where reported'); + }); + worker.on('message', function(value) { + test.assertEqual( + "worked", + value, + "test comunication" + ); + workerDone = true; + if (callbackDone) + callbackDone(); + }); + worker.postMessage('do it!') + } + }], + function(win, done) { + (callbackDone = function() { + if (workerDone) { + test.assertEqual( + 'worked', + win.document.body.getAttribute('JEP-107'), + 'attribute should be modified' + ); + done(); + } + })(); + } + ); +}; + +exports.testCommunication2 = function(test) { + let callbackDone = null, + window; + + testPageMod(test, "about:license", [{ + include: "about:*", + contentScriptWhen: 'start', + contentScript: 'new ' + function WorkerScope() { + document.documentElement.setAttribute('AUQLUE', 42); + window.addEventListener('load', function listener() { + self.postMessage('onload'); + }, false); + self.on("message", function() { + self.postMessage(document.documentElement.getAttribute("test")) + }); + }, + onAttach: function(worker) { + worker.on('error', function(e) { + test.fail('Errors where reported'); + }); + worker.on('message', function(msg) { + if ('onload' == msg) { + test.assertEqual( + '42', + window.document.documentElement.getAttribute('AUQLUE'), + 'PageMod scripts executed in order' + ); + window.document.documentElement.setAttribute('test', 'changes in window'); + worker.postMessage('get window.test') + } else { + test.assertEqual( + 'changes in window', + msg, + 'PageMod test #2: second script has run' + ) + callbackDone(); + } + }); + } + }], + function(win, done) { + window = win; + callbackDone = done; + } + ); +}; + +exports.testEventEmitter = function(test) { + let workerDone = false, + callbackDone = null; + + testPageMod(test, "about:", [{ + include: "about:*", + contentScript: 'new ' + function WorkerScope() { + self.port.on('addon-to-content', function(data) { + self.port.emit('content-to-addon', data); + }); + }, + onAttach: function(worker) { + worker.on('error', function(e) { + test.fail('Errors were reported : '+e); + }); + worker.port.on('content-to-addon', function(value) { + test.assertEqual( + "worked", + value, + "EventEmitter API works!" + ); + if (callbackDone) + callbackDone(); + else + workerDone = true; + }); + worker.port.emit('addon-to-content', 'worked'); + } + }], + function(win, done) { + if (workerDone) + done(); + else + callbackDone = done; + } + ); +}; + +// Execute two concurrent page mods on same document to ensure that their +// JS contexts are different +exports.testMixedContext = function(test) { + let doneCallback = null; + let messages = 0; + let modObject = { + include: "data:text/html;charset=utf-8,", + contentScript: 'new ' + function WorkerScope() { + // Both scripts will execute this, + // context is shared if one script see the other one modification. + let isContextShared = "sharedAttribute" in document; + self.postMessage(isContextShared); + document.sharedAttribute = true; + }, + onAttach: function(w) { + w.on("message", function (isContextShared) { + if (isContextShared) { + test.fail("Page mod contexts are mixed."); + doneCallback(); + } + else if (++messages == 2) { + test.pass("Page mod contexts are different."); + doneCallback(); + } + }); + } + }; + testPageMod(test, "data:text/html;charset=utf-8,", [modObject, modObject], + function(win, done) { + doneCallback = done; + } + ); +}; + +exports.testHistory = function(test) { + // We need a valid url in order to have a working History API. + // (i.e do not work on data: or about: pages) + // Test bug 679054. + let url = require("sdk/self").data.url("test-page-mod.html"); + let callbackDone = null; + testPageMod(test, url, [{ + include: url, + contentScriptWhen: 'end', + contentScript: 'new ' + function WorkerScope() { + history.pushState({}, "", "#"); + history.replaceState({foo: "bar"}, "", "#"); + self.postMessage(history.state); + }, + onAttach: function(worker) { + worker.on('message', function (data) { + test.assertEqual(JSON.stringify(data), JSON.stringify({foo: "bar"}), + "History API works!"); + callbackDone(); + }); + } + }], + function(win, done) { + callbackDone = done; + } + ); +}; + +exports.testRelatedTab = function(test) { + test.waitUntilDone(); + + let tab; + let { PageMod } = require("sdk/page-mod"); + let pageMod = new PageMod({ + include: "about:*", + onAttach: function(worker) { + test.assertEqual(tab, worker.tab, "Worker.tab is valid"); + pageMod.destroy(); + tab.close(); + test.done(); + } + }); + + tabs.open({ + url: "about:", + onOpen: function onOpen(t) { + tab = t; + } + }); + +}; + +exports.testWorksWithExistingTabs = function(test) { + test.waitUntilDone(); + + let url = "data:text/html;charset=utf-8," + encodeURI("Test unique document"); + let { PageMod } = require("sdk/page-mod"); + tabs.open({ + url: url, + onReady: function onReady(tab) { + let pageMod = new PageMod({ + include: url, + attachTo: ["existing", "top", "frame"], + onAttach: function(worker) { + test.assertEqual(tab, worker.tab, "A worker has been created on this existing tab"); + pageMod.destroy(); + tab.close(); + test.done(); + } + }); + } + }); + +}; + +exports['test tab worker on message'] = function(test) { + test.waitUntilDone(); + + let { browserWindows } = require("sdk/windows"); + let tabs = require("sdk/tabs"); + let { PageMod } = require("sdk/page-mod"); + + let url1 = "data:text/html;charset=utf-8,<title>tab1</title><h1>worker1.tab</h1>"; + let url2 = "data:text/html;charset=utf-8,<title>tab2</title><h1>worker2.tab</h1>"; + let worker1 = null; + + let mod = PageMod({ + include: "data:text/html*", + contentScriptWhen: "ready", + contentScript: "self.postMessage('#1');", + onAttach: function onAttach(worker) { + worker.on("message", function onMessage() { + this.tab.attach({ + contentScriptWhen: "ready", + contentScript: "self.postMessage({ url: window.location.href, title: document.title });", + onMessage: function onMessage(data) { + test.assertEqual(this.tab.url, data.url, "location is correct"); + test.assertEqual(this.tab.title, data.title, "title is correct"); + if (this.tab.url === url1) { + worker1 = this; + tabs.open({ url: url2, inBackground: true }); + } + else if (this.tab.url === url2) { + mod.destroy(); + worker1.tab.close(); + worker1.destroy(); + worker.tab.close(); + worker.destroy(); + test.done(); + } + } + }); + }); + } + }); + + tabs.open(url1); +}; + +exports.testAutomaticDestroy = function(test) { + test.waitUntilDone(); + let loader = Loader(module); + + let pageMod = loader.require("sdk/page-mod").PageMod({ + include: "about:*", + contentScriptWhen: "start", + onAttach: function(w) { + test.fail("Page-mod should have been detroyed during module unload"); + } + }); + + // Unload the page-mod module so that our page mod is destroyed + loader.unload(); + + // Then create a second tab to ensure that it is correctly destroyed + let tabs = require("sdk/tabs"); + tabs.open({ + url: "about:", + onReady: function onReady(tab) { + test.pass("check automatic destroy"); + tab.close(); + test.done(); + } + }); + +} + +exports['test attachment to tabs only'] = function(test) { + test.waitUntilDone(); + + let { PageMod } = require('sdk/page-mod'); + let openedTab = null; // Tab opened in openTabWithIframe() + let workerCount = 0; + + let mod = PageMod({ + include: 'data:text/html*', + contentScriptWhen: 'start', + contentScript: '', + onAttach: function onAttach(worker) { + if (worker.tab === openedTab) { + if (++workerCount == 3) { + test.pass('Succesfully applied to tab documents and its iframe'); + worker.destroy(); + mod.destroy(); + test.done(); + } + } + else { + test.fail('page-mod attached to a non-tab document'); + } + } + }); + + function openHiddenFrame() { + console.info('Open iframe in hidden window'); + let hiddenFrames = require('sdk/frame/hidden-frame'); + let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({ + onReady: function () { + let element = this.element; + element.addEventListener('DOMContentLoaded', function onload() { + element.removeEventListener('DOMContentLoaded', onload, false); + hiddenFrames.remove(hiddenFrame); + openToplevelWindow(); + }, false); + element.setAttribute('src', 'data:text/html;charset=utf-8,foo'); + } + })); + } + + function openToplevelWindow() { + console.info('Open toplevel window'); + let win = open('data:text/html;charset=utf-8,bar'); + win.addEventListener('DOMContentLoaded', function onload() { + win.removeEventListener('DOMContentLoaded', onload, false); + win.close(); + openBrowserIframe(); + }, false); + } + + function openBrowserIframe() { + console.info('Open iframe in browser window'); + let window = require('sdk/deprecated/window-utils').activeBrowserWindow; + let document = window.document; + let iframe = document.createElement('iframe'); + iframe.setAttribute('type', 'content'); + iframe.setAttribute('src', 'data:text/html;charset=utf-8,foobar'); + iframe.addEventListener('DOMContentLoaded', function onload() { + iframe.removeEventListener('DOMContentLoaded', onload, false); + iframe.parentNode.removeChild(iframe); + openTabWithIframes(); + }, false); + document.documentElement.appendChild(iframe); + } + + // Only these three documents will be accepted by the page-mod + function openTabWithIframes() { + console.info('Open iframes in a tab'); + let subContent = '<iframe src="data:text/html;charset=utf-8,sub frame" />' + let content = '<iframe src="data:text/html,' + + encodeURIComponent(subContent) + '" />'; + require('sdk/tabs').open({ + url: 'data:text/html;charset=utf-8,' + encodeURIComponent(content), + onOpen: function onOpen(tab) { + openedTab = tab; + } + }); + } + + openHiddenFrame(); +}; + +exports['test111 attachTo [top]'] = function(test) { + test.waitUntilDone(); + + let { PageMod } = require('sdk/page-mod'); + + let subContent = '<iframe src="data:text/html;charset=utf-8,sub frame" />' + let content = '<iframe src="data:text/html;charset=utf-8,' + + encodeURIComponent(subContent) + '" />'; + let topDocumentURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(content) + + let workerCount = 0; + + let mod = PageMod({ + include: 'data:text/html*', + contentScriptWhen: 'start', + contentScript: 'self.postMessage(document.location.href);', + attachTo: ['top'], + onAttach: function onAttach(worker) { + if (++workerCount == 1) { + worker.on('message', function (href) { + test.assertEqual(href, topDocumentURL, + "worker on top level document only"); + worker.destroy(); + mod.destroy(); + test.done(); + }); + } + else { + test.fail('page-mod attached to a non-top document'); + } + } + }); + + require('sdk/tabs').open(topDocumentURL); +}; + +exports['test111 attachTo [frame]'] = function(test) { + test.waitUntilDone(); + + let { PageMod } = require('sdk/page-mod'); + + let subFrameURL = 'data:text/html;charset=utf-8,subframe'; + let subContent = '<iframe src="' + subFrameURL + '" />'; + let frameURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(subContent); + let content = '<iframe src="' + frameURL + '" />'; + let topDocumentURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(content) + + let workerCount = 0, messageCount = 0; + + function onMessage(href) { + if (href == frameURL) + test.pass("worker on first frame"); + else if (href == subFrameURL) + test.pass("worker on second frame"); + else + test.fail("worker on unexpected document: " + href); + this.destroy(); + if (++messageCount == 2) { + mod.destroy(); + test.done(); + } + } + let mod = PageMod({ + include: 'data:text/html*', + contentScriptWhen: 'start', + contentScript: 'self.postMessage(document.location.href);', + attachTo: ['frame'], + onAttach: function onAttach(worker) { + if (++workerCount <= 2) { + worker.on('message', onMessage); + } + else { + test.fail('page-mod attached to a non-frame document'); + } + } + }); + + require('sdk/tabs').open(topDocumentURL); +}; + +exports.testContentScriptOptionsOption = function(test) { + test.waitUntilDone(); + + let callbackDone = null; + testPageMod(test, "about:", [{ + include: "about:*", + contentScript: "self.postMessage( [typeof self.options.d, self.options] );", + contentScriptWhen: "end", + contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}}, + onAttach: function(worker) { + worker.on('message', function(msg) { + test.assertEqual( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' ); + test.assertEqual( typeof msg[1], 'object', 'object as contentScriptOptions' ); + test.assertEqual( msg[1].a, true, 'boolean in contentScriptOptions' ); + test.assertEqual( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' ); + test.assertEqual( msg[1].c, 'string', 'string in contentScriptOptions' ); + callbackDone(); + }); + } + }], + function(win, done) { + callbackDone = done; + } + ); +}; + +exports.testPageModCss = function(test) { + let [pageMod] = testPageMod(test, + 'data:text/html;charset=utf-8,<div style="background: silver">css test</div>', [{ + include: "data:*", + contentStyle: "div { height: 100px; }", + contentStyleFile: + require("sdk/self").data.url("pagemod-css-include-file.css") + }], + function(win, done) { + let div = win.document.querySelector("div"); + test.assertEqual( + div.clientHeight, + 100, + "PageMod contentStyle worked" + ); + test.assertEqual( + div.offsetHeight, + 120, + "PageMod contentStyleFile worked" + ); + done(); + } + ); +}; + +exports.testPageModCssList = function(test) { + let [pageMod] = testPageMod(test, + 'data:text/html;charset=utf-8,<div style="width:320px; max-width: 480px!important">css test</div>', [{ + include: "data:*", + contentStyleFile: [ + // Highlight evaluation order in this list + "data:text/css;charset=utf-8,div { border: 1px solid black; }", + "data:text/css;charset=utf-8,div { border: 10px solid black; }", + // Highlight evaluation order between contentStylesheet & contentStylesheetFile + "data:text/cs;charset=utf-8s,div { height: 1000px; }", + // Highlight precedence between the author and user style sheet + "data:text/css;charset=utf-8,div { width: 200px; max-width: 640px!important}", + ], + contentStyle: [ + "div { height: 10px; }", + "div { height: 100px; }" + ] + }], + function(win, done) { + let div = win.document.querySelector("div"), + style = win.getComputedStyle(div); + + test.assertEqual( + div.clientHeight, + 100, + "PageMod contentStyle list works and is evaluated after contentStyleFile" + ); + + test.assertEqual( + div.offsetHeight, + 120, + "PageMod contentStyleFile list works" + ); + + test.assertEqual( + style.width, + "320px", + "PageMod author/user style sheet precedence works" + ); + + test.assertEqual( + style.maxWidth, + "640px", + "PageMod author/user style sheet precedence with !important works" + ); + + done(); + } + ); +}; + +exports.testPageModCssDestroy = function(test) { + let [pageMod] = testPageMod(test, + 'data:text/html;charset=utf-8,<div style="width:200px">css test</div>', [{ + include: "data:*", + contentStyle: "div { width: 100px!important; }" + }], + + function(win, done) { + let div = win.document.querySelector("div"), + style = win.getComputedStyle(div); + + test.assertEqual( + style.width, + "100px", + "PageMod contentStyle worked" + ); + + pageMod.destroy(); + test.assertEqual( + style.width, + "200px", + "PageMod contentStyle is removed after destroy" + ); + + done(); + + } + ); +}; + +exports.testPageModCssAutomaticDestroy = function(test) { + test.waitUntilDone(); + let loader = Loader(module); + + let pageMod = loader.require("page-mod").PageMod({ + include: "data:*", + contentStyle: "div { width: 100px!important; }" + }); + + tabs.open({ + url: "data:text/html;charset=utf-8,<div style='width:200px'>css test</div>", + + onReady: function onReady(tab) { + let browserWindow = windowUtils.activeBrowserWindow; + let win = getTabContentWindow(getActiveTab(browserWindow)); + + let div = win.document.querySelector("div"), + style = win.getComputedStyle(div); + + test.assertEqual( + style.width, + "100px", + "PageMod contentStyle worked" + ); + + loader.unload(); + + test.assertEqual( + style.width, + "200px", + "PageMod contentStyle is removed after loader's unload" + ); + + tab.close(); + test.done(); + } + }); +}; + + +exports.testPageModTimeout = function(test) { + test.waitUntilDone(); + let tab = null + let loader = Loader(module); + let { PageMod } = loader.require("page-mod"); + + let mod = PageMod({ + include: "data:*", + contentScript: Isolate(function() { + var id = setTimeout(function() { + self.port.emit("fired", id) + }, 10) + self.port.emit("scheduled", id); + }), + onAttach: function(worker) { + worker.port.on("scheduled", function(id) { + test.pass("timer was scheduled") + worker.port.on("fired", function(data) { + test.assertEqual(id, data, "timer was fired") + tab.close() + worker.destroy() + loader.unload() + test.done() + }) + }) + } + }); + + tabs.open({ + url: "data:text/html;charset=utf-8,timeout", + onReady: function($) { tab = $ } + }) +} + + +exports.testPageModcancelTimeout = function(test) { + test.waitUntilDone(); + let tab = null + let loader = Loader(module); + let { PageMod } = loader.require("page-mod"); + + let mod = PageMod({ + include: "data:*", + contentScript: Isolate(function() { + var id1 = setTimeout(function() { + self.port.emit("failed") + }, 10) + var id2 = setTimeout(function() { + self.port.emit("timeout") + }, 100) + clearTimeout(id1) + }), + onAttach: function(worker) { + worker.port.on("failed", function() { + test.fail("cancelled timeout fired") + }) + worker.port.on("timeout", function(id) { + test.pass("timer was scheduled") + tab.close() + worker.destroy() + loader.unload() + test.done() + }) + } + }); + + tabs.open({ + url: "data:text/html;charset=utf-8,cancell timeout", + onReady: function($) { tab = $ } + }) +} + +exports.testBug803529 = function(test) { + test.waitUntilDone(); + + let subIFrame = '<iframe src="data:text/html;charset=utf-8,sub frame" />' + let iFrame = '<iframe src="data:text/html;charset=utf-8,' + encodeURIComponent(subIFrame) + '" />'; + let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iFrame) + + let counter = 0; + let tab = openTab(getMostRecentBrowserWindow(), url); + let window = getTabContentWindow(tab); + + function wait4Iframes() { + if (window.document.readyState != "complete" || + getFrames(window).length != 2) { + return; + } + + let pagemod = pageMod.PageMod({ + include: ["*", "data:*"], + attachTo: ["existing", "frame"], + contentScriptWhen: 'ready', + onAttach: function(mod) { + if (++counter != 2) return; + test.pass('page mod attached to iframe'); + timer.setTimeout(function() { + pagemod.destroy(); + closeTab(tab); + test.done(); + }, 0); + } + }); + } + + window.addEventListener("load", wait4Iframes, false); +}; + +exports.testIFramePostMessage = function(test) { + test.waitUntilDone(); + + tabs.open({ + url: data.url("test-iframe.html"), + onReady: function(tab) { + var worker = tab.attach({ + contentScriptFile: data.url('test-iframe.js'), + contentScript: ' var iframePath = \'' + data.url('test-iframe-postmessage.html') + '\'', + onMessage: function(msg) { + test.assertEqual(msg.first, 'a string'); + test.assert(msg.second[1], "array"); + test.assertEqual(typeof msg.third, 'object'); + + worker.destroy(); + tab.close(function() test.done()); + } + }); + } + }); +}; diff --git a/tools/addon-sdk-1.12/test/test-page-worker.js b/tools/addon-sdk-1.12/test/test-page-worker.js new file mode 100644 index 0000000..469c19b --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-page-worker.js @@ -0,0 +1,384 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +let tests = {}, Pages, Page; +const { Loader } = require('sdk/test/loader'); + +const ERR_DESTROYED = + "Couldn't find the worker to receive this message. " + + "The script may not be initialized yet, or may already have been unloaded."; + +tests.testSimplePageCreation = function(test) { + test.waitUntilDone(); + + let page = new Page({ + contentScript: "self.postMessage(window.location.href)", + contentScriptWhen: "end", + onMessage: function (message) { + test.assertEqual(message, "about:blank", + "Page Worker should start with a blank page by default"); + test.assertEqual(this, page, "The 'this' object is the page itself."); + test.done(); + } + }); +} + +/* + * Tests that we can't be tricked by document overloads as we have access + * to wrapped nodes + */ +tests.testWrappedDOM = function(test) { + test.waitUntilDone(); + + let page = Page({ + allow: { script: true }, + contentURL: "data:text/html;charset=utf-8,<script>document.getElementById=3;window.scrollTo=3;</script>", + contentScript: "window.addEventListener('load', function () " + + "self.postMessage([typeof(document.getElementById), " + + "typeof(window.scrollTo)]), true)", + onMessage: function (message) { + test.assertEqual(message[0], + "function", + "getElementById from content script is the native one"); + + test.assertEqual(message[1], + "function", + "scrollTo from content script is the native one"); + + test.done(); + } + }); +} + +/* +// We do not offer unwrapped access to DOM since bug 601295 landed +// See 660780 to track progress of unwrap feature +tests.testUnwrappedDOM = function(test) { + test.waitUntilDone(); + + let page = Page({ + allow: { script: true }, + contentURL: "data:text/html;charset=utf-8,<script>document.getElementById=3;window.scrollTo=3;</script>", + contentScript: "window.addEventListener('load', function () " + + "self.postMessage([typeof(unsafeWindow.document.getElementById), " + + "typeof(unsafeWindow.scrollTo)]), true)", + onMessage: function (message) { + test.assertEqual(message[0], + "number", + "document inside page is free to be changed"); + + test.assertEqual(message[1], + "number", + "window inside page is free to be changed"); + + test.done(); + } + }); +} +*/ + +tests.testPageProperties = function(test) { + let page = new Page(); + + for each (let prop in ['contentURL', 'allow', 'contentScriptFile', + 'contentScript', 'contentScriptWhen', 'on', + 'postMessage', 'removeListener']) { + test.assert(prop in page, prop + " property is defined on page."); + } + + test.assert(function () page.postMessage("foo") || true, + "postMessage doesn't throw exception on page."); +} + +tests.testConstructorAndDestructor = function(test) { + test.waitUntilDone(); + + let loader = Loader(module); + let Pages = loader.require("sdk/page-worker"); + let global = loader.sandbox("sdk/page-worker"); + + let pagesReady = 0; + + let page1 = Pages.Page({ + contentScript: "self.postMessage('')", + contentScriptWhen: "end", + onMessage: pageReady + }); + let page2 = Pages.Page({ + contentScript: "self.postMessage('')", + contentScriptWhen: "end", + onMessage: pageReady + }); + + test.assertNotEqual(page1, page2, + "Page 1 and page 2 should be different objects."); + + function pageReady() { + if (++pagesReady == 2) { + page1.destroy(); + page2.destroy(); + + test.assert(isDestroyed(page1), "page1 correctly unloaded."); + test.assert(isDestroyed(page2), "page2 correctly unloaded."); + + loader.unload(); + test.done(); + } + } +} + +tests.testAutoDestructor = function(test) { + test.waitUntilDone(); + + let loader = Loader(module); + let Pages = loader.require("sdk/page-worker"); + + let page = Pages.Page({ + contentScript: "self.postMessage('')", + contentScriptWhen: "end", + onMessage: function() { + loader.unload(); + test.assert(isDestroyed(page), "Page correctly unloaded."); + test.done(); + } + }); +} + +tests.testValidateOptions = function(test) { + test.assertRaises( + function () Page({ contentURL: 'home' }), + "The `contentURL` option must be a valid URL.", + "Validation correctly denied a non-URL contentURL" + ); + + test.assertRaises( + function () Page({ onMessage: "This is not a function."}), + "The event listener must be a function.", + "Validation correctly denied a non-function onMessage." + ); + + test.pass("Options validation is working."); +} + +tests.testContentAndAllowGettersAndSetters = function(test) { + test.waitUntilDone(); + let content = "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=3;</script>"; + let page = Page({ + contentURL: content, + contentScript: "self.postMessage(window.localStorage.allowScript)", + contentScriptWhen: "end", + onMessage: step0 + }); + + function step0(message) { + test.assertEqual(message, "3", + "Correct value expected for allowScript - 3"); + test.assertEqual(page.contentURL, content, + "Correct content expected"); + page.removeListener('message', step0); + page.on('message', step1); + page.allow = { script: false }; + page.contentURL = content = + "data:text/html;charset=utf-8,<script>window.localStorage.allowScript='f'</script>"; + } + + function step1(message) { + test.assertEqual(message, "3", + "Correct value expected for allowScript - 3"); + test.assertEqual(page.contentURL, content, "Correct content expected"); + page.removeListener('message', step1); + page.on('message', step2); + page.allow = { script: true }; + page.contentURL = content = + "data:text/html;charset=utf-8,<script>window.localStorage.allowScript='g'</script>"; + } + + function step2(message) { + test.assertEqual(message, "g", + "Correct value expected for allowScript - g"); + test.assertEqual(page.contentURL, content, "Correct content expected"); + page.removeListener('message', step2); + page.on('message', step3); + page.allow.script = false; + page.contentURL = content = + "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=3</script>"; + } + + function step3(message) { + test.assertEqual(message, "g", + "Correct value expected for allowScript - g"); + test.assertEqual(page.contentURL, content, "Correct content expected"); + page.removeListener('message', step3); + page.on('message', step4); + page.allow.script = true; + page.contentURL = content = + "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=4</script>"; + } + + function step4(message) { + test.assertEqual(message, "4", + "Correct value expected for allowScript - 4"); + test.assertEqual(page.contentURL, content, "Correct content expected"); + test.done(); + } + +} + +tests.testOnMessageCallback = function(test) { + test.waitUntilDone(); + + Page({ + contentScript: "self.postMessage('')", + contentScriptWhen: "end", + onMessage: function() { + test.pass("onMessage callback called"); + test.done(); + } + }); +} + +tests.testMultipleOnMessageCallbacks = function(test) { + test.waitUntilDone(); + + let count = 0; + let page = Page({ + contentScript: "self.postMessage('')", + contentScriptWhen: "end", + onMessage: function() count += 1 + }); + page.on('message', function() count += 2); + page.on('message', function() count *= 3); + page.on('message', function() + test.assertEqual(count, 9, "All callbacks were called, in order.")); + page.on('message', function() test.done()); + +} + +tests.testLoadContentPage = function(test) { + + test.waitUntilDone(); + + let page = Page({ + onMessage: function(message) { + // The message is an array whose first item is the test method to call + // and the rest of whose items are arguments to pass it. + test[message.shift()].apply(test, message); + }, + contentURL: require("sdk/self").data.url("test-page-worker.html"), + contentScriptFile: require("sdk/self").data.url("test-page-worker.js"), + contentScriptWhen: "ready" + }); + +} + +tests.testAllowScriptDefault = function(test) { + + test.waitUntilDone(); + + let page = Page({ + onMessage: function(message) { + test.assert(message, "Script is allowed to run by default."); + test.done(); + }, + contentURL: "data:text/html;charset=utf-8,<script>document.documentElement.setAttribute('foo', 3);</script>", + contentScript: "self.postMessage(document.documentElement.getAttribute('foo'))", + contentScriptWhen: "ready" + }); +} + +tests.testAllowScript = function(test) { + + test.waitUntilDone(); + + let page = Page({ + onMessage: function(message) { + test.assert(message, "Script runs when allowed to do so."); + test.done(); + }, + allow: { script: true }, + contentURL: "data:text/html;charset=utf-8,<script>document.documentElement.setAttribute('foo', 3);</script>", + contentScript: "self.postMessage(document.documentElement.hasAttribute('foo') && " + + " document.documentElement.getAttribute('foo') == 3)", + contentScriptWhen: "ready" + }); +} + +tests.testPingPong = function(test) { + test.waitUntilDone(); + let page = Page({ + contentURL: 'data:text/html;charset=utf-8,ping-pong', + contentScript: 'self.on("message", function(message) self.postMessage("pong"));' + + 'self.postMessage("ready");', + onMessage: function(message) { + if ('ready' == message) { + page.postMessage('ping'); + } + else { + test.assert(message, 'pong', 'Callback from contentScript'); + test.done(); + } + } + }); +}; + +tests.testMultipleDestroys = function(test) { + let page = Page(); + page.destroy(); + page.destroy(); + test.pass("Multiple destroys should not cause an error"); +}; + +exports.testContentScriptOptionsOption = function(test) { + test.waitUntilDone(); + + let page = new Page({ + contentScript: "self.postMessage( [typeof self.options.d, self.options] );", + contentScriptWhen: "end", + contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}}, + onMessage: function(msg) { + test.assertEqual( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' ); + test.assertEqual( typeof msg[1], 'object', 'object as contentScriptOptions' ); + test.assertEqual( msg[1].a, true, 'boolean in contentScriptOptions' ); + test.assertEqual( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' ); + test.assertEqual( msg[1].c, 'string', 'string in contentScriptOptions' ); + test.done(); + } + }); +}; + +function isDestroyed(page) { + try { + page.postMessage("foo"); + } + catch (err if err.message == ERR_DESTROYED) { + return true; + } + return false; +} + + +let pageWorkerSupported = true; + +try { + Pages = require("sdk/page-worker"); + Page = Pages.Page; +} +catch (ex if ex.message == [ + "The page-worker module currently supports only Firefox and Thunderbird. ", + "In the future, we would like it to support other applications, however. ", + "Please see https://bugzilla.mozilla.org/show_bug.cgi?id=546740 for more ", + "information." + ].join("")) { + pageWorkerSupported = false; +} + +if (pageWorkerSupported) { + for (let test in tests) { + exports[test] = tests[test]; + } +} else { + exports.testPageWorkerNotSupported = function(test) { + test.pass("The page-worker module is not supported on this app."); + } +} diff --git a/tools/addon-sdk-1.12/test/test-panel.js b/tools/addon-sdk-1.12/test/test-panel.js new file mode 100644 index 0000000..bdce83d --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-panel.js @@ -0,0 +1,486 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +let { Cc, Ci } = require("chrome"); +let panels = require('sdk/panel'); +let tests = {}, panels, Panel; +const { Loader } = require('sdk/test/loader'); +const timer = require("sdk/timers"); + +tests.testPanel = function(test) { + test.waitUntilDone(); + let panel = Panel({ + contentURL: "about:buildconfig", + contentScript: "self.postMessage(1); self.on('message', function() self.postMessage(2));", + onMessage: function (message) { + test.assertEqual(this, panel, "The 'this' object is the panel."); + switch(message) { + case 1: + test.pass("The panel was loaded."); + panel.postMessage(''); + break; + case 2: + test.pass("The panel posted a message and received a response."); + panel.destroy(); + test.done(); + break; + } + } + }); +}; + +tests.testPanelEmit = function(test) { + test.waitUntilDone(); + let panel = Panel({ + contentURL: "about:buildconfig", + contentScript: "self.port.emit('loaded');" + + "self.port.on('addon-to-content', " + + " function() self.port.emit('received'));", + }); + panel.port.on("loaded", function () { + test.pass("The panel was loaded and sent a first event."); + panel.port.emit("addon-to-content"); + }); + panel.port.on("received", function () { + test.pass("The panel posted a message and received a response."); + panel.destroy(); + test.done(); + }); +}; + +tests.testPanelEmitEarly = function(test) { + test.waitUntilDone(); + let panel = Panel({ + contentURL: "about:buildconfig", + contentScript: "self.port.on('addon-to-content', " + + " function() self.port.emit('received'));", + }); + panel.port.on("received", function () { + test.pass("The panel posted a message early and received a response."); + panel.destroy(); + test.done(); + }); + panel.port.emit("addon-to-content"); +}; + +tests.testShowHidePanel = function(test) { + test.waitUntilDone(); + let panel = Panel({ + contentScript: "self.postMessage('')", + contentScriptWhen: "end", + onMessage: function (message) { + panel.show(); + }, + onShow: function () { + test.pass("The panel was shown."); + test.assertEqual(this, panel, "The 'this' object is the panel."); + test.assertEqual(this.isShowing, true, "panel.isShowing == true."); + panel.hide(); + }, + onHide: function () { + test.pass("The panel was hidden."); + test.assertEqual(this, panel, "The 'this' object is the panel."); + test.assertEqual(this.isShowing, false, "panel.isShowing == false."); + panel.destroy(); + test.done(); + } + }); +}; + +tests.testDocumentReload = function(test) { + test.waitUntilDone(); + let content = + "<script>" + + "setTimeout(function () {" + + " window.location = 'about:blank';" + + "}, 250);" + + "</script>"; + let messageCount = 0; + let panel = Panel({ + contentURL: "data:text/html;charset=utf-8," + encodeURIComponent(content), + contentScript: "self.postMessage(window.location.href)", + onMessage: function (message) { + messageCount++; + if (messageCount == 1) { + test.assertMatches(message, /data:text\/html/, "First document had a content script"); + } + else if (messageCount == 2) { + test.assertEqual(message, "about:blank", "Second document too"); + panel.destroy(); + test.done(); + } + } + }); +}; + +tests.testParentResizeHack = function(test) { + let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator). + getMostRecentWindow("navigator:browser"); + let docShell = browserWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + if (!("allowWindowControl" in docShell)) { + // bug 635673 is not fixed in this firefox build + test.pass("allowWindowControl attribute that allow to fix browser window " + + "resize is not available on this build."); + return; + } + + test.waitUntilDone(30000); + + let previousWidth = browserWindow.outerWidth, previousHeight = browserWindow.outerHeight; + + let content = "<script>" + + "function contentResize() {" + + " resizeTo(200,200);" + + " resizeBy(200,200);" + + "}" + + "</script>" + + "Try to resize browser window"; + let panel = Panel({ + contentURL: "data:text/html;charset=utf-8," + encodeURIComponent(content), + contentScript: "self.on('message', function(message){" + + " if (message=='resize') " + + " unsafeWindow.contentResize();" + + "});", + contentScriptWhen: "ready", + onMessage: function (message) { + + }, + onShow: function () { + panel.postMessage('resize'); + timer.setTimeout(function () { + test.assertEqual(previousWidth,browserWindow.outerWidth,"Size doesn't change by calling resizeTo/By/..."); + test.assertEqual(previousHeight,browserWindow.outerHeight,"Size doesn't change by calling resizeTo/By/..."); + panel.destroy(); + test.done(); + },0); + } + }); + panel.show(); +} + +tests.testResizePanel = function(test) { + test.waitUntilDone(); + + // These tests fail on Linux if the browser window in which the panel + // is displayed is not active. And depending on what other tests have run + // before this one, it might not be (the untitled window in which the test + // runner executes is often active). So we make sure the browser window + // is focused by focusing it before running the tests. Then, to be the best + // possible test citizen, we refocus whatever window was focused before we + // started running these tests. + + let activeWindow = Cc["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Ci.nsIWindowWatcher). + activeWindow; + let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator). + getMostRecentWindow("navigator:browser"); + + + function onFocus() { + browserWindow.removeEventListener("focus", onFocus, true); + + let panel = Panel({ + contentScript: "self.postMessage('')", + contentScriptWhen: "end", + height: 10, + width: 10, + onMessage: function (message) { + panel.show(); + }, + onShow: function () { + panel.resize(100,100); + panel.hide(); + }, + onHide: function () { + test.assert((panel.width == 100) && (panel.height == 100), + "The panel was resized."); + if (activeWindow) + activeWindow.focus(); + test.done(); + } + }); + } + + if (browserWindow === activeWindow) { + onFocus(); + } + else { + browserWindow.addEventListener("focus", onFocus, true); + browserWindow.focus(); + } +}; + +tests.testHideBeforeShow = function(test) { + test.waitUntilDone(); + let showCalled = false; + let panel = Panel({ + onShow: function () { + showCalled = true; + }, + onHide: function () { + test.assert(!showCalled, 'must not emit show if was hidden before'); + test.done(); + } + }); + panel.show(); + panel.hide(); +}; + +tests.testSeveralShowHides = function(test) { + test.waitUntilDone(); + let hideCalled = 0; + let panel = panels.Panel({ + contentURL: "about:buildconfig", + onShow: function () { + panel.hide(); + }, + onHide: function () { + hideCalled++; + if (hideCalled < 3) + panel.show(); + else { + test.pass("onHide called three times as expected"); + test.done(); + } + } + }); + panel.on('error', function(e) { + test.fail('error was emitted:' + e.message + '\n' + e.stack); + }); + panel.show(); +}; + +tests.testAnchorAndArrow = function(test) { + test.waitUntilDone(20000); + let count = 0; + function newPanel(tab, anchor) { + let panel = panels.Panel({ + contentURL: "data:text/html;charset=utf-8,<html><body style='padding: 0; margin: 0; " + + "background: gray; text-align: center;'>Anchor: " + + anchor.id + "</body></html>", + width: 200, + height: 100, + onShow: function () { + count++; + panel.destroy(); + if (count==5) { + test.pass("All anchored panel test displayed"); + tab.close(function () { + test.done(); + }); + } + } + }); + panel.show(anchor); + } + + let tabs= require("sdk/tabs"); + let url = 'data:text/html;charset=utf-8,' + + '<html><head><title>foo</title></head><body>' + + '<style>div {background: gray; position: absolute; width: 300px; ' + + 'border: 2px solid black;}</style>' + + '<div id="tl" style="top: 0px; left: 0px;">Top Left</div>' + + '<div id="tr" style="top: 0px; right: 0px;">Top Right</div>' + + '<div id="bl" style="bottom: 0px; left: 0px;">Bottom Left</div>' + + '<div id="br" style="bottom: 0px; right: 0px;">Bottom right</div>' + + '</body></html>'; + + tabs.open({ + url: url, + onReady: function(tab) { + let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator). + getMostRecentWindow("navigator:browser"); + let window = browserWindow.content; + newPanel(tab, window.document.getElementById('tl')); + newPanel(tab, window.document.getElementById('tr')); + newPanel(tab, window.document.getElementById('bl')); + newPanel(tab, window.document.getElementById('br')); + let anchor = browserWindow.document.getElementById("identity-box"); + newPanel(tab, anchor); + } + }); + + + +}; + +tests.testPanelTextColor = function(test) { + test.waitUntilDone(); + let html = "<html><head><style>body {color: yellow}</style></head>" + + "<body><p>Foo</p></body></html>"; + let panel = Panel({ + contentURL: "data:text/html;charset=utf-8," + encodeURI(html), + contentScript: "self.port.emit('color', " + + "window.getComputedStyle(document.body.firstChild, null). " + + " getPropertyValue('color'));" + }); + panel.port.on("color", function (color) { + test.assertEqual(color, "rgb(255, 255, 0)", + "The panel text color style is preserved when a style exists."); + panel.destroy(); + test.done(); + }); +}; + +// Bug 696552: Ensure panel.contentURL modification support +tests.testChangeContentURL = function(test) { + test.waitUntilDone(); + + let panel = Panel({ + contentURL: "about:blank", + contentScript: "self.port.emit('ready', document.location.href);" + }); + let count = 0; + panel.port.on("ready", function (location) { + count++; + if (count == 1) { + test.assertEqual(location, "about:blank"); + test.assertEqual(panel.contentURL, "about:blank"); + panel.contentURL = "about:buildconfig"; + } + else { + test.assertEqual(location, "about:buildconfig"); + test.assertEqual(panel.contentURL, "about:buildconfig"); + panel.destroy(); + test.done(); + } + }); +}; + +function makeEventOrderTest(options) { + let expectedEvents = []; + + return function(test) { + let panel = panels.Panel({ contentURL: "about:buildconfig" }); + + function expect(event, cb) { + expectedEvents.push(event); + panel.on(event, function() { + test.assertEqual(event, expectedEvents.shift()); + if (cb) + timer.setTimeout(cb, 1); + }); + return {then: expect}; + } + + test.waitUntilDone(); + options.test(test, expect, panel); + } +} + +tests.testAutomaticDestroy = function(test) { + let loader = Loader(module); + let panel = loader.require("sdk/panel").Panel({ + contentURL: "about:buildconfig", + contentScript: + "self.port.on('event', function() self.port.emit('event-back'));" + }); + + loader.unload(); + + panel.port.on("event-back", function () { + test.fail("Panel should have been destroyed on module unload"); + }); + panel.port.emit("event"); + test.pass("check automatic destroy"); +}; + +tests.testWaitForInitThenShowThenDestroy = makeEventOrderTest({ + test: function(test, expect, panel) { + expect('inited', function() { panel.show(); }). + then('show', function() { panel.destroy(); }). + then('hide', function() { test.done(); }); + } +}); + +tests.testShowThenWaitForInitThenDestroy = makeEventOrderTest({ + test: function(test, expect, panel) { + panel.show(); + expect('inited'). + then('show', function() { panel.destroy(); }). + then('hide', function() { test.done(); }); + } +}); + +tests.testShowThenHideThenDestroy = makeEventOrderTest({ + test: function(test, expect, panel) { + panel.show(); + expect('show', function() { panel.hide(); }). + then('hide', function() { panel.destroy(); test.done(); }); + } +}); + +tests.testContentURLOption = function(test) { + const URL_STRING = "about:buildconfig"; + const HTML_CONTENT = "<html><title>Test</title><p>This is a test.</p></html>"; + + let (panel = Panel({ contentURL: URL_STRING })) { + test.pass("contentURL accepts a string URL."); + test.assertEqual(panel.contentURL, URL_STRING, + "contentURL is the string to which it was set."); + } + + let dataURL = "data:text/html;charset=utf-8," + encodeURIComponent(HTML_CONTENT); + let (panel = Panel({ contentURL: dataURL })) { + test.pass("contentURL accepts a data: URL."); + } + + let (panel = Panel({})) { + test.assert(panel.contentURL == null, + "contentURL is undefined."); + } + + test.assertRaises(function () Panel({ contentURL: "foo" }), + "The `contentURL` option must be a valid URL.", + "Panel throws an exception if contentURL is not a URL."); +}; + +exports.testContentScriptOptionsOption = function(test) { + test.waitUntilDone(); + + let loader = Loader(module); + let panel = loader.require("sdk/panel").Panel({ + contentScript: "self.postMessage( [typeof self.options.d, self.options] );", + contentScriptWhen: "end", + contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}}, + onMessage: function(msg) { + test.assertEqual( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' ); + test.assertEqual( typeof msg[1], 'object', 'object as contentScriptOptions' ); + test.assertEqual( msg[1].a, true, 'boolean in contentScriptOptions' ); + test.assertEqual( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' ); + test.assertEqual( msg[1].c, 'string', 'string in contentScriptOptions' ); + test.done(); + } + }); +}; + +let panelSupported = true; + +try { + panels = require("sdk/panel"); + Panel = panels.Panel; +} +catch(ex if ex.message == [ + "The panel module currently supports only Firefox. In the future ", + "we would like it to support other applications, however. Please see ", + "https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps ", + "for more information." + ].join("")) { + panelSupported = false; +} + +if (panelSupported) { + for (let test in tests) + exports[test] = tests[test]; +} +else { + exports.testPanelNotSupported = function(test) { + test.pass("The panel module is not supported on this app."); + } +} diff --git a/tools/addon-sdk-1.12/test/test-passwords-utils.js b/tools/addon-sdk-1.12/test/test-passwords-utils.js new file mode 100644 index 0000000..3773090 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-passwords-utils.js @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { store, search, remove } = require("sdk/passwords/utils"); + +exports["test store requires `password` field"] = function(assert) { + assert.throws(function() { + store({ username: "foo", realm: "bar" }); + }, "`password` is required"); +}; + +exports["test store requires `username` field"] = function(assert) { + assert.throws(function() { + store({ password: "foo", realm: "bar" }); + }, "`username` is required"); +}; + +exports["test store requires `realm` field"] = function(assert) { + assert.throws(function() { + store({ username: "foo", password: "bar" }); + }, "`password` is required"); +}; + +exports["test can't store same login twice"] = function(assert) { + let options = { username: "user", password: "pass", realm: "realm" }; + store(options); + assert.throws(function() { + store(options); + }, "can't store same pass twice"); + remove(options); +}; + +exports["test remove throws if no login found"] = function(assert) { + assert.throws(function() { + remove({ username: "foo", password: "bar", realm: "baz" }); + }, "can't remove unstored credentials"); +}; + +exports["test addon associated credentials"] = function(assert) { + let options = { username: "foo", password: "bar", realm: "baz" }; + store(options); + + assert.ok(search().length, "credential was stored"); + assert.ok(search(options).length, "stored credential found"); + assert.ok(search({ username: options.username }).length, "found by username"); + assert.ok(search({ password: options.password }).length, "found by password"); + assert.ok(search({ realm: options.realm }).length, "found by realm"); + + let credential = search(options)[0]; + assert.equal(credential.url.indexOf("addon:"), 0, + "`addon:` uri is used for add-on associated credentials"); + assert.equal(credential.username, options.username, "username matches"); + assert.equal(credential.password, options.password, "password matches"); + assert.equal(credential.realm, options.realm, "realm matches"); + assert.equal(credential.formSubmitURL, null, + "`formSubmitURL` is `null` for add-on associated credentials"); + assert.equal(credential.usernameField, "", "usernameField is empty"); + assert.equal(credential.passwordField, "", "passwordField is empty"); + + remove(search(options)[0]); + assert.ok(!search(options).length, "remove worked"); +}; + +exports["test web page associated credentials"] = function(assert) { + let options = { + url: "http://www.example.com", + formSubmitURL: "http://login.example.com", + username: "user", + password: "pass", + usernameField: "user-f", + passwordField: "pass-f" + }; + store({ + url: "http://www.example.com/login", + formSubmitURL: "http://login.example.com/foo/authenticate.cgi", + username: options.username, + password: options.password, + usernameField: options.usernameField, + passwordField: options.passwordField + }); + + assert.ok(search().length, "credential was stored"); + assert.ok(search(options).length, "stored credential found"); + assert.ok(search({ username: options.username }).length, "found by username"); + assert.ok(search({ password: options.password }).length, "found by password"); + assert.ok(search({ formSubmitURL: options.formSubmitURL }).length, + "found by formSubmitURL"); + assert.ok(search({ usernameField: options.usernameField }).length, + "found by usernameField"); + assert.ok(search({ passwordField: options.passwordField }).length, + "found by passwordField"); + + let credential = search(options)[0]; + assert.equal(credential.url, options.url, "url matches"); + assert.equal(credential.username, options.username, "username matches"); + assert.equal(credential.password, options.password, "password matches"); + assert.equal(credential.realm, null, "realm is "); + assert.equal(credential.formSubmitURL, options.formSubmitURL, + "`formSubmitURL` matches"); + assert.equal(credential.usernameField, options.usernameField, + "usernameField matches"); + assert.equal(credential.passwordField, options.passwordField, + "passwordField matches"); + + remove(search(options)[0]); + assert.ok(!search(options).length, "remove worked"); +}; + +exports["test site authentication credentials"] = function(assert) { + let options = { + url: "http://test.authentication.com", + username: "u", + password: "p", + realm: "r" + }; + + store(options); + assert.ok(search().length, "credential was stored"); + assert.ok(search(options).length, "stored credential found"); + assert.ok(search({ username: options.username }).length, "found by username"); + assert.ok(search({ password: options.password }).length, "found by password"); + assert.ok(search({ realm: options.realm }).length, "found by realm"); + assert.ok(search({ url: options.url }).length, "found by url"); + + let credential = search(options)[0]; + assert.equal(credential.url, options.url, "url matches"); + assert.equal(credential.username, options.username, "username matches"); + assert.equal(credential.password, options.password, "password matches"); + assert.equal(credential.realm, options.realm, "realm matches"); + assert.equal(credential.formSubmitURL, null, + "`formSubmitURL` is `null` for site authentication credentials"); + assert.equal(credential.usernameField, "", "usernameField is empty"); + assert.equal(credential.passwordField, "", "passwordField is empty"); + + remove(search(options)[0]); + assert.ok(!search(options).length, "remove worked"); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-passwords.js b/tools/addon-sdk-1.12/test/test-passwords.js new file mode 100644 index 0000000..04e2181 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-passwords.js @@ -0,0 +1,280 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { store, search, remove } = require("sdk/passwords"); + +exports["test store requires `password` field"] = function(assert, done) { + store({ + username: "foo", + realm: "bar", + onComplete: function onComplete() { + assert.fail("onComplete should not be called"); + }, + onError: function onError() { + assert.pass("'`password` is required"); + done(); + } + }); +}; + +exports["test store requires `username` field"] = function(assert, done) { + store({ + password: "foo", + realm: "bar", + onComplete: function onComplete() { + assert.fail("onComplete should not be called"); + }, + onError: function onError() { + assert.pass("'`username` is required"); + done(); + } + }); +}; + +exports["test onComplete is optional"] = function(assert, done) { + store({ + realm: "bla", + username: "bla", + password: "bla", + onError: function onError() { + assert.fail("onError was called"); + } + }); + assert.pass("exception is not thrown if `onComplete is missing") + done(); +}; + +exports["test exceptions in onComplete are reported"] = function(assert, done) { + store({ + realm: "throws", + username: "error", + password: "boom!", + onComplete: function onComplete(error) { + throw new Error("Boom!") + }, + onError: function onError(error) { + assert.equal(error.message, "Boom!", "Error thrown is reported"); + done(); + } + }); +}; + +exports["test store requires `realm` field"] = function(assert, done) { + store({ + username: "foo", + password: "bar", + onComplete: function onComplete() { + assert.fail("onComplete should not be called"); + }, + onError: function onError() { + assert.pass("'`realm` is required"); + done(); + } + }); +}; + +exports["test can't store same login twice"] = function(assert, done) { + store({ + username: "user", + password: "pass", + realm: "realm", + onComplete: function onComplete() { + assert.pass("credential saved"); + + store({ + username: "user", + password: "pass", + realm: "realm", + onComplete: function onComplete() { + assert.fail("onComplete should not be called"); + }, + onError: function onError() { + assert.pass("re-saving credential failed"); + + remove({ + username: "user", + password: "pass", + realm: "realm", + onComplete: function onComplete() { + assert.pass("credential was removed"); + done(); + }, + onError: function onError() { + assert.fail("remove should not fail"); + } + }); + } + }); + }, + onError: function onError() { + assert.fail("onError should not be called"); + } + }); +}; + +exports["test remove fails if no login found"] = function(assert, done) { + remove({ + username: "foo", + password: "bar", + realm: "baz", + onComplete: function onComplete() { + assert.fail("should not be able to remove unstored credentials"); + }, + onError: function onError() { + assert.pass("can't remove unstored credentials"); + done(); + } + }); +}; + +exports["test addon associated credentials"] = function(assert, done) { + store({ + username: "foo", + password: "bar", + realm: "baz", + onComplete: function onComplete() { + search({ + username: "foo", + password: "bar", + realm: "baz", + onComplete: function onComplete([credential]) { + assert.equal(credential.url.indexOf("addon:"), 0, + "`addon:` uri is used for add-on credentials"); + assert.equal(credential.username, "foo", + "username matches"); + assert.equal(credential.password, "bar", + "password matches"); + assert.equal(credential.realm, "baz", "realm matches"); + assert.equal(credential.formSubmitURL, null, + "`formSubmitURL` is `null` for add-on credentials"); + assert.equal(credential.usernameField, "", "usernameField is empty"); + assert.equal(credential.passwordField, "", "passwordField is empty"); + + remove({ + username: credential.username, + password: credential.password, + realm: credential.realm, + onComplete: function onComplete() { + assert.pass("credential is removed"); + done(); + }, + onError: function onError() { + assert.fail("onError should not be called"); + } + }); + }, + onError: function onError() { + assert.fail("onError should not be called"); + } + }); + }, + onError: function onError() { + assert.fail("onError should not be called"); + } + }); +}; + +exports["test web page associated credentials"] = function(assert, done) { + store({ + url: "http://bar.foo.com/authentication/?login", + formSubmitURL: "http://login.foo.com/authenticate.cgi", + username: "user", + password: "pass", + usernameField: "user-f", + passwordField: "pass-f", + onComplete: function onComplete() { + search({ + username: "user", + password: "pass", + url: "http://bar.foo.com", + formSubmitURL: "http://login.foo.com", + onComplete: function onComplete([credential]) { + assert.equal(credential.url, "http://bar.foo.com", "url matches"); + assert.equal(credential.username, "user", "username matches"); + assert.equal(credential.password, "pass", "password matches"); + assert.equal(credential.realm, null, "realm is null"); + assert.equal(credential.formSubmitURL, "http://login.foo.com", + "formSubmitURL matches"); + assert.equal(credential.usernameField, "user-f", + "usernameField is matches"); + assert.equal(credential.passwordField, "pass-f", + "passwordField matches"); + + remove({ + url: credential.url, + formSubmitURL: credential.formSubmitURL, + username: credential.username, + password: credential.password, + usernameField: credential.usernameField, + passwordField: credential.passwordField, + + onComplete: function onComplete() { + assert.pass("credential is removed"); + done(); + }, + onError: function onError(e) { + assert.fail("onError should not be called"); + } + }); + }, + onError: function onError() { + assert.fail("onError should not be called"); + } + }); + }, + onError: function onError() { + assert.fail("onError should not be called"); + } + }); +}; + +exports["test site authentication credentials"] = function(assert, done) { + store({ + url: "http://authentication.com", + username: "U", + password: "P", + realm: "R", + onComplete: function onComplete() { + search({ + url: "http://authentication.com", + username: "U", + password: "P", + realm: "R", + onComplete: function onComplete([credential]) { + assert.equal(credential.url,"http://authentication.com", + "url matches"); + assert.equal(credential.username, "U", "username matches"); + assert.equal(credential.password, "P", "password matches"); + assert.equal(credential.realm, "R", "realm matches"); + assert.equal(credential.formSubmitURL, null, "formSubmitURL is null"); + assert.equal(credential.usernameField, "", "usernameField is empty"); + assert.equal(credential.passwordField, "", "passwordField is empty"); + + remove({ + url: credential.url, + username: credential.username, + password: credential.password, + realm: credential.realm, + onComplete: function onComplete() { + assert.pass("credential is removed"); + done(); + }, + onError: function onError() { + assert.fail("onError should not be called"); + } + }); + }, + onError: function onError() { + assert.fail("onError should not be called"); + } + }); + }, + onError: function onError() { + assert.fail("onError should not be called"); + } + }); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-plain-text-console.js b/tools/addon-sdk-1.12/test/test-plain-text-console.js new file mode 100644 index 0000000..ce41b07 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-plain-text-console.js @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.testPlainTextConsole = function(test) { + var prints = []; + function print(message) { + prints.push(message); + } + function lastPrint() { + var last = prints.slice(-1)[0]; + prints = []; + return last; + } + + var Console = require("sdk/console/plain-text").PlainTextConsole; + var con = new Console(print); + + test.pass("PlainTextConsole instantiates"); + + con.log('testing', 1, [2, 3, 4]); + test.assertEqual(lastPrint(), "info: " + require("sdk/self").name + ": testing 1 2,3,4\n", + "PlainTextConsole.log() must work."); + + con.info('testing', 1, [2, 3, 4]); + test.assertEqual(lastPrint(), "info: " + require("sdk/self").name + ": testing 1 2,3,4\n", + "PlainTextConsole.info() must work."); + + con.warn('testing', 1, [2, 3, 4]); + test.assertEqual(lastPrint(), "warning: " + require("sdk/self").name + ": testing 1 2,3,4\n", + "PlainTextConsole.warn() must work."); + + con.error('testing', 1, [2, 3, 4]); + test.assertEqual(lastPrint(), "error: " + require("sdk/self").name + ": testing 1 2,3,4\n", + "PlainTextConsole.error() must work."); + + con.debug('testing', 1, [2, 3, 4]); + test.assertEqual(lastPrint(), "debug: " + require("sdk/self").name + ": testing 1 2,3,4\n", + "PlainTextConsole.debug() must work."); + + con.log('testing', undefined); + test.assertEqual(lastPrint(), "info: " + require("sdk/self").name + ": testing undefined\n", + "PlainTextConsole.log() must stringify undefined."); + + con.log('testing', null); + test.assertEqual(lastPrint(), "info: " + require("sdk/self").name + ": testing null\n", + "PlainTextConsole.log() must stringify null."); + + con.log("testing", { toString: function() "obj.toString()" }); + test.assertEqual(lastPrint(), "info: " + require("sdk/self").name + ": testing obj.toString()\n", + "PlainTextConsole.log() must stringify custom toString."); + + con.log("testing", { toString: function() { throw "fail!"; } }); + test.assertEqual(lastPrint(), "info: " + require("sdk/self").name + ": testing <toString() error>\n", + "PlainTextConsole.log() must stringify custom bad toString."); + + con.exception(new Error("blah")); + var tbLines = prints[0].split("\n"); + test.assertEqual(tbLines[0], "error: " + require("sdk/self").name + ": An exception occurred."); + test.assertEqual(tbLines[1], "Traceback (most recent call last):"); + test.assertEqual(tbLines.slice(-2)[0], "Error: blah"); + + prints = []; + con.trace(); + tbLines = prints[0].split("\n"); + test.assertEqual(tbLines[0], "info: " + require("sdk/self").name + ": Traceback (most recent call last):"); + test.assertEqual(tbLines.slice(-2)[0].trim(), "con.trace();"); +}; diff --git a/tools/addon-sdk-1.12/test/test-preferences-service.js b/tools/addon-sdk-1.12/test/test-preferences-service.js new file mode 100644 index 0000000..89cb2f5 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-preferences-service.js @@ -0,0 +1,143 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const prefs = require("sdk/preferences/service"); +const Branch = prefs.Branch; +const { Cc, Ci, Cu } = require("chrome"); +const BundleService = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService); + +const specialChars = "!@#$%^&*()_-=+[]{}~`\'\"<>,./?;:"; + +exports.testReset = function(test) { + prefs.reset("test_reset_pref"); + test.assertEqual(prefs.has("test_reset_pref"), false); + test.assertEqual(prefs.isSet("test_reset_pref"), false); + prefs.set("test_reset_pref", 5); + test.assertEqual(prefs.has("test_reset_pref"), true); + test.assertEqual(prefs.isSet("test_reset_pref"), true); + test.assertEqual(prefs.keys("test_reset_pref").toString(), "test_reset_pref"); +}; + +exports.testGetAndSet = function(test) { + let svc = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefService). + getBranch(null); + svc.setCharPref("test_set_get_pref", "a normal string"); + test.assertEqual(prefs.get("test_set_get_pref"), "a normal string", + "preferences-service should read from " + + "application-wide preferences service"); + + prefs.set("test_set_get_pref.integer", 1); + test.assertEqual(prefs.get("test_set_get_pref.integer"), 1, + "set/get integer preference should work"); + + test.assertEqual( + prefs.keys("test_set_get_pref").sort().toString(), + ["test_set_get_pref.integer","test_set_get_pref"].sort().toString()); + + prefs.set("test_set_get_number_pref", 42); + test.assertRaises( + function() { prefs.set("test_set_get_number_pref", 3.14159); }, + "cannot store non-integer number: 3.14159", + "setting a float preference should raise an error" + ); + test.assertEqual(prefs.get("test_set_get_number_pref"), 42, + "bad-type write attempt should not overwrite"); + + // 0x80000000 (no), 0x7fffffff (yes), -0x80000000 (yes), -0x80000001 (no) + test.assertRaises( + function() { prefs.set("test_set_get_number_pref", Math.pow(2, 31)); }, + ("you cannot set the test_set_get_number_pref pref to the number " + + "2147483648, as number pref values must be in the signed 32-bit " + + "integer range -(2^31) to 2^31-1. To store numbers outside that " + + "range, store them as strings."), + "setting an int pref outside the range -(2^31) to 2^31-1 shouldn't work" + ); + test.assertEqual(prefs.get("test_set_get_number_pref"), 42, + "out-of-range write attempt should not overwrite 1"); + prefs.set("test_set_get_number_pref", Math.pow(2, 31)-1); + test.assertEqual(prefs.get("test_set_get_number_pref"), 0x7fffffff, + "in-range write attempt should work 1"); + prefs.set("test_set_get_number_pref", -Math.pow(2, 31)); + test.assertEqual(prefs.get("test_set_get_number_pref"), -0x80000000, + "in-range write attempt should work 2"); + test.assertRaises( + function() { prefs.set("test_set_get_number_pref", -0x80000001); }, + ("you cannot set the test_set_get_number_pref pref to the number " + + "-2147483649, as number pref values must be in the signed 32-bit " + + "integer range -(2^31) to 2^31-1. To store numbers outside that " + + "range, store them as strings."), + "setting an int pref outside the range -(2^31) to 2^31-1 shouldn't work" + ); + test.assertEqual(prefs.get("test_set_get_number_pref"), -0x80000000, + "out-of-range write attempt should not overwrite 2"); + + + prefs.set("test_set_get_pref.string", "foo"); + test.assertEqual(prefs.get("test_set_get_pref.string"), "foo", + "set/get string preference should work"); + + prefs.set("test_set_get_pref.boolean", true); + test.assertEqual(prefs.get("test_set_get_pref.boolean"), true, + "set/get boolean preference should work"); + + prefs.set("test_set_get_unicode_pref", String.fromCharCode(960)); + test.assertEqual(prefs.get("test_set_get_unicode_pref"), + String.fromCharCode(960), + "set/get unicode preference should work"); + + var unsupportedValues = [null, [], undefined]; + unsupportedValues.forEach( + function(value) { + test.assertRaises( + function() { prefs.set("test_set_pref", value); }, + ("can't set pref test_set_pref to value '" + value + "'; " + + "it isn't a string, integer, or boolean"), + "Setting a pref to " + uneval(value) + " should raise error" + ); + }); +}; + +exports.testPrefClass = function(test) { + var branch = Branch("test_foo"); + + test.assertEqual(branch.test, undefined, "test_foo.test is undefined"); + branch.test = true; + test.assertEqual(branch.test, true, "test_foo.test is true"); + delete branch.test; + test.assertEqual(branch.test, undefined, "test_foo.test is undefined"); +}; + +exports.testGetSetLocalized = function(test) { + let prefName = "general.useragent.locale"; + + // Ensure that "general.useragent.locale" is a 'localized' pref + let bundleURL = "chrome://global/locale/intl.properties"; + prefs.setLocalized(prefName, bundleURL); + + // Fetch the expected value directly from the property file + let expectedValue = BundleService.createBundle(bundleURL). + GetStringFromName(prefName). + toLowerCase(); + + test.assertEqual(prefs.getLocalized(prefName).toLowerCase(), + expectedValue, + "get localized preference"); + + // Undo our modification + prefs.reset(prefName); +} + +// TEST: setting and getting preferences with special characters work +exports.testSpecialChars = function(test) { + let chars = specialChars.split(''); + const ROOT = "test."; + + chars.forEach(function(char) { + let rand = Math.random() + ""; + prefs.set(ROOT+char, rand); + test.assertEqual(prefs.get(ROOT+char), rand, "setting pref with a name that is a special char, " + char + ", worked!"); + }); +}; diff --git a/tools/addon-sdk-1.12/test/test-preferences-target.js b/tools/addon-sdk-1.12/test/test-preferences-target.js new file mode 100644 index 0000000..b2bb077 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-preferences-target.js @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { PrefsTarget } = require('sdk/preferences/event-target'); +const { get, set, reset } = require('sdk/preferences/service'); +const { Loader } = require('sdk/test/loader'); +const { setTimeout } = require('sdk/timers'); + +const root = PrefsTarget(); + +exports.testPrefsTarget = function(test) { + test.waitUntilDone(); + + let loader = Loader(module); + let pt = loader.require('sdk/preferences/event-target').PrefsTarget({}); + let name = 'test'; + + test.assertEqual(get(name, ''), '', 'test pref is blank'); + + pt.once(name, function() { + test.assertEqual(pt.prefs[name], 2, 'test pref is 2'); + + pt.once(name, function() { + test.fail('should not have heard a pref change'); + }); + loader.unload(); + root.once(name, function() { + test.pass('test pref was changed'); + reset(name); + + // NOTE: using setTimeout to make sure that the other listener had + // a chance to fail + // end test + setTimeout(function() test.done()); + }); + set(name, 3); + }); + + pt.prefs[name] = 2; +}; diff --git a/tools/addon-sdk-1.12/test/test-private-browsing.js b/tools/addon-sdk-1.12/test/test-private-browsing.js new file mode 100644 index 0000000..de73892 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-private-browsing.js @@ -0,0 +1,204 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +let { Cc,Ci } = require("chrome"); +const timer = require("sdk/timers"); +const { LoaderWithHookedConsole, pb } = require("private-browsing-helper"); + +let pbService; +// Currently, only Firefox implements the private browsing service. +if (require("sdk/system/xul-app").is("Firefox")) { + pbService = Cc["@mozilla.org/privatebrowsing;1"]. + getService(Ci.nsIPrivateBrowsingService); +} + +if (pbService) { + // tests that isActive has the same value as the private browsing service + // expects + exports.testGetIsActive = function (test) { + test.assertEqual(pb.isActive, false, + "private-browsing.isActive is correct without modifying PB service"); + + pbService.privateBrowsingEnabled = true; + test.assert(pb.isActive, + "private-browsing.isActive is correct after modifying PB service"); + + // Switch back to normal mode. + pbService.privateBrowsingEnabled = false; + }; + + // tests that activating does put the browser into private browsing mode + exports.testActivateDeactivate = function (test) { + test.waitUntilDone(); + pb.once("start", function onStart() { + test.assertEqual(pbService.privateBrowsingEnabled, true, + "private browsing mode was activated"); + pb.deactivate(); + }); + pb.once("stop", function onStop() { + test.assertEqual(pbService.privateBrowsingEnabled, false, + "private browsing mode was deactivate"); + test.done(); + }); + pb.activate(); + }; + + exports.testStart = function(test) { + test.waitUntilDone(); + pb.on("start", function onStart() { + test.assertEqual(this, pb, "`this` should be private-browsing module"); + test.assert(pbService.privateBrowsingEnabled, + 'private mode is active when "start" event is emitted'); + test.assert(pb.isActive, + '`isActive` is `true` when "start" event is emitted'); + pb.removeListener("start", onStart); + test.done(); + }); + pb.activate(); + }; + + exports.testStop = function(test) { + test.waitUntilDone(); + pb.on("stop", function onStop() { + test.assertEqual(this, pb, "`this` should be private-browsing module"); + test.assertEqual(pbService.privateBrowsingEnabled, false, + "private mode is disabled when stop event is emitted"); + test.assertEqual(pb.isActive, false, + "`isActive` is `false` when stop event is emitted"); + pb.removeListener("stop", onStop); + test.done(); + }); + pb.activate(); + pb.deactivate(); + }; + + exports.testAutomaticUnload = function(test) { + test.waitUntilDone(); + // Create another private browsing instance and unload it + let { loader, errors } = LoaderWithHookedConsole(); + let pb2 = loader.require("sdk/private-browsing"); + let called = false; + pb2.on("start", function onStart() { + called = true; + test.fail("should not be called:x"); + }); + loader.unload(); + + // Then switch to private mode in order to check that the previous instance + // is correctly destroyed + pb.activate(); + pb.once("start", function onStart() { + timer.setTimeout(function () { + test.assert(!called, + "First private browsing instance is destroyed and inactive"); + + // Must reset to normal mode, so that next test starts with it. + pb.deactivate(); + test.done(); + }, 0); + }); + }; + + exports.testBothListeners = function(test) { + test.waitUntilDone(); + let stop = false; + let start = false; + + function onStop() { + test.assertEqual(stop, false, + "stop callback must be called only once"); + test.assertEqual(pbService.privateBrowsingEnabled, false, + "private mode is disabled when stop event is emitted"); + test.assertEqual(pb.isActive, false, + "`isActive` is `false` when stop event is emitted"); + + pb.on("start", finish); + pb.removeListener("start", onStart); + pb.removeListener("start", onStart2); + pb.activate(); + stop = true; + } + + function onStart() { + test.assertEqual(false, start, + "stop callback must be called only once"); + test.assert(pbService.privateBrowsingEnabled, + "private mode is active when start event is emitted"); + test.assert(pb.isActive, + "`isActive` is `true` when start event is emitted"); + + pb.on("stop", onStop); + pb.deactivate(); + start = true; + } + + function onStart2() { + test.assert(start, "start listener must be called already"); + test.assertEqual(false, stop, "stop callback must not be called yet"); + } + + function finish() { + test.assert(pbService.privateBrowsingEnabled, true, + "private mode is active when start event is emitted"); + test.assert(pb.isActive, + "`isActive` is `true` when start event is emitted"); + + pb.removeListener("start", finish); + pb.removeListener("stop", onStop); + + pb.deactivate(); + pb.once("stop", function () { + test.assertEqual(pbService.privateBrowsingEnabled, false); + test.assertEqual(pb.isActive, false); + + test.done(); + }); + } + + pb.on("start", onStart); + pb.on("start", onStart2); + pbService.privateBrowsingEnabled = true; + }; + + exports["test activate private mode via handler"] = function(test) { + const tabs = require("sdk/tabs"); + + test.waitUntilDone(); + function onReady(tab) { + if (tab.url == "about:robots") + tab.close(function() pb.activate()); + } + function cleanup(tab) { + if (tab.url == "about:") { + tabs.removeListener("ready", cleanup); + tab.close(function onClose() { + test.done(); + }); + } + } + + tabs.on("ready", onReady); + pb.once("start", function onStart() { + test.pass("private mode was activated"); + pb.deactivate(); + }); + pb.once("stop", function onStop() { + test.pass("private mode was deactivated"); + tabs.removeListener("ready", onReady); + tabs.on("ready", cleanup); + }); + tabs.once("open", function onOpen() { + tabs.open("about:robots"); + }); + tabs.open("about:"); + }; +} +else { + // tests for the case where private browsing doesn't exist + exports.testNoImpl = function (test) { + test.assertEqual(pb.isActive, false, + "pb.isActive returns false when private browsing isn't supported"); + }; +} diff --git a/tools/addon-sdk-1.12/test/test-promise.js b/tools/addon-sdk-1.12/test/test-promise.js new file mode 100644 index 0000000..744baeb --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-promise.js @@ -0,0 +1,327 @@ +/* vim:set ts=2 sw=2 sts=2 expandtab */ +/*jshint asi: true undef: true es5: true node: true devel: true + forin: true */ +/*global define: true */ + +'use strict'; + +var core = require('sdk/core/promise'), + defer = core.defer, resolve = core.resolve, reject = core.reject, + promised = core.promised + +exports['test all observers are notified'] = function(assert, done) { + var expected = 'Taram pam param!' + var deferred = defer() + var pending = 10, i = 0 + + function resolved(value) { + assert.equal(value, expected, 'value resolved as expected: #' + pending) + if (!--pending) done() + } + + while (i++ < pending) deferred.promise.then(resolved) + + deferred.resolve(expected) +} + +exports['test exceptions dont stop notifications'] = function(assert, done) { + var threw = false, boom = Error('Boom!') + var deferred = defer() + + var promise2 = deferred.promise.then(function() { + threw = true + throw boom + }) + + deferred.promise.then(function() { + assert.ok(threw, 'observer is called even though previos one threw') + promise2.then(function() { + assert.fail('should not resolve') + }, function(reason) { + assert.equal(reason, boom, 'rejects to thrown error') + done() + }) + }) + + deferred.resolve('go!') +} + +exports['test subsequent resolves are ignored'] = function(assert, done) { + var deferred = defer() + deferred.resolve(1) + deferred.resolve(2) + deferred.reject(3) + + deferred.promise.then(function(actual) { + assert.equal(actual, 1, 'resolves to first value') + }, function() { + assert.fail('must not reject') + }) + deferred.promise.then(function(actual) { + assert.equal(actual, 1, 'subsequent resolutions are ignored') + done() + }, function() { + assert.fail('must not reject') + }) +} + +exports['test subsequent rejections are ignored'] = function(assert, done) { + var deferred = defer() + deferred.reject(1) + deferred.resolve(2) + deferred.reject(3) + + deferred.promise.then(function(actual) { + assert.fail('must not resolve') + }, function(actual) { + assert.equal(actual, 1, 'must reject to first') + }) + deferred.promise.then(function(actual) { + assert.fail('must not resolve') + }, function(actual) { + assert.equal(actual, 1, 'must reject to first') + done() + }) +} + +exports['test error recovery'] = function(assert, done) { + var boom = Error('Boom!') + var deferred = defer() + + deferred.promise.then(function() { + assert.fail('rejected promise should not resolve') + }, function(reason) { + assert.equal(reason, boom, 'rejection reason delivered') + return 'recovery' + }).then(function(value) { + assert.equal(value, 'recovery', 'error handled by a handler') + done() + }) + + deferred.reject(boom) +} + + +exports['test error recovery with promise'] = function(assert, done) { + var deferred = defer() + + deferred.promise.then(function() { + assert.fail('must reject') + }, function(actual) { + assert.equal(actual, 'reason', 'rejected') + var deferred = defer() + deferred.resolve('recovery') + return deferred.promise + }).then(function(actual) { + assert.equal(actual, 'recovery', 'recorvered via promise') + var deferred = defer() + deferred.reject('error') + return deferred.promise + }).then(null, function(actual) { + assert.equal(actual, 'error', 'rejected via promise') + var deferred = defer() + deferred.reject('end') + return deferred.promise + }).then(null, function(actual) { + assert.equal(actual, 'end', 'rejeced via promise') + done() + }) + + deferred.reject('reason') +} + +exports['test propagation'] = function(assert, done) { + var d1 = defer(), d2 = defer(), d3 = defer() + + d1.promise.then(function(actual) { + assert.equal(actual, 'expected', 'resolves to expected value') + done() + }) + + d1.resolve(d2.promise) + d2.resolve(d3.promise) + d3.resolve('expected') +} + +exports['test chaining'] = function(assert, done) { + var boom = Error('boom'), brax = Error('braxXXx') + var deferred = defer() + + deferred.promise.then().then().then(function(actual) { + assert.equal(actual, 2, 'value propagates unchanged') + return actual + 2 + }).then(null, function(reason) { + assert.fail('should not reject') + }).then(function(actual) { + assert.equal(actual, 4, 'value propagates through if not handled') + throw boom + }).then(function(actual) { + assert.fail('exception must reject promise') + }).then().then(null, function(actual) { + assert.equal(actual, boom, 'reason propagates unchanged') + throw brax + }).then().then(null, function(actual) { + assert.equal(actual, brax, 'reason changed becase of exception') + return 'recovery' + }).then(function(actual) { + assert.equal(actual, 'recovery', 'recovered from error') + done() + }) + + deferred.resolve(2) +} + + +exports['test reject'] = function(assert, done) { + var expected = Error('boom') + + reject(expected).then(function() { + assert.fail('should reject') + }, function(actual) { + assert.equal(actual, expected, 'rejected with expected reason') + }).then(function() { + done() + }) +} + +exports['test resolve to rejected'] = function(assert, done) { + var expected = Error('boom') + var deferred = defer() + + deferred.promise.then(function() { + assert.fail('should reject') + }, function(actual) { + assert.equal(actual, expected, 'rejected with expected failure') + }).then(function() { + done() + }) + + deferred.resolve(reject(expected)) +} + +exports['test resolve'] = function(assert, done) { + var expected = 'value' + resolve(expected).then(function(actual) { + assert.equal(actual, expected, 'resolved as expected') + }).then(function() { + done() + }) +} + +exports['test resolve with prototype'] = function(assert, done) { + var seventy = resolve(70, { + subtract: function subtract(y) { + return this.then(function(x) { return x - y }) + } + }) + + seventy.subtract(17).then(function(actual) { + assert.equal(actual, 70 - 17, 'resolves to expected') + done() + }) +} + +exports['test promised with normal args'] = function(assert, done) { + var sum = promised(function(x, y) { return x + y }) + + sum(7, 8).then(function(actual) { + assert.equal(actual, 7 + 8, 'resolves as expected') + done() + }) +} + +exports['test promised with promise args'] = function(assert, done) { + var sum = promised(function(x, y) { return x + y }) + var deferred = defer() + + sum(11, deferred.promise).then(function(actual) { + assert.equal(actual, 11 + 24, 'resolved as expected') + done() + }) + + deferred.resolve(24) +} + +exports['test promised with prototype'] = function(assert, done) { + var deferred = defer() + var numeric = {} + numeric.subtract = promised(function(y) { return this - y }, numeric) + + var sum = promised(function(x, y) { return x + y }, numeric) + + sum(7, 70). + subtract(14). + subtract(deferred.promise). + subtract(5). + then(function(actual) { + assert.equal(actual, 7 + 70 - 14 - 23 - 5, 'resolved as expected') + done() + }) + + deferred.resolve(23) +} + +exports['test promised error handleing'] = function(assert, done) { + var expected = Error('boom') + var f = promised(function() { + throw expected + }) + + f().then(function() { + assert.fail('should reject') + }, function(actual) { + assert.equal(actual, expected, 'rejected as expected') + done() + }) +} + +exports['test return promise form promised'] = function(assert, done) { + var f = promised(function() { + return resolve(17) + }) + + f().then(function(actual) { + assert.equal(actual, 17, 'resolves to a promise resolution') + done() + }) +} + +exports['test promised returning failure'] = function(assert, done) { + var expected = Error('boom') + var f = promised(function() { + return reject(expected) + }) + + f().then(function() { + assert.fail('must reject') + }, function(actual) { + assert.equal(actual, expected, 'rejects with expected reason') + done() + }) +} + +exports['test promised are greedy'] = function(assert, done) { + var runs = 0 + var f = promised(function() { ++runs }) + var promise = f() + assert.equal(runs, 1, 'promised runs task right away') + done() +} + +exports['test arrays should not flatten'] = function(assert, done) { + var a = defer() + var b = defer() + + var combine = promised(function(str, arr) { + assert.equal(str, 'Hello', 'Array was not flattened') + assert.deepEqual(arr, [ 'my', 'friend' ]) + }) + + combine(a.promise, b.promise).then(done) + + + a.resolve('Hello') + b.resolve([ 'my', 'friend' ]) +} + +require("test").run(exports) diff --git a/tools/addon-sdk-1.12/test/test-querystring.js b/tools/addon-sdk-1.12/test/test-querystring.js new file mode 100644 index 0000000..9c016cf --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-querystring.js @@ -0,0 +1,206 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; + +// test using assert +var qs = require('sdk/querystring'); + +// folding block, commented to pass gjslint +// {{{ +// [ wonkyQS, canonicalQS, obj ] +var qsTestCases = [ + ['foo=918854443121279438895193', + 'foo=918854443121279438895193', + {'foo': '918854443121279438895193'}], + ['foo=bar', 'foo=bar', {'foo': 'bar'}], + //['foo=bar&foo=quux', 'foo=bar&foo=quux', {'foo': ['bar', 'quux']}], + ['foo=1&bar=2', 'foo=1&bar=2', {'foo': '1', 'bar': '2'}], + // ['my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F', + // 'my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F', + // {'my weird field': 'q1!2"\'w$5&7/z8)?' }], + ['foo%3Dbaz=bar', 'foo%3Dbaz=bar', {'foo=baz': 'bar'}], + ['foo=baz=bar', 'foo=baz%3Dbar', {'foo': 'baz=bar'}], + /* + ['str=foo&arr=1&arr=2&arr=3&somenull=&undef=', + 'str=foo&arr=1&arr=2&arr=3&somenull=&undef=', + { 'str': 'foo', + 'arr': ['1', '2', '3'], + 'somenull': '', + 'undef': ''}], + */ + //[' foo = bar ', '%20foo%20=%20bar%20', {' foo ': ' bar '}], + // disable test that fails ['foo=%zx', 'foo=%25zx', {'foo': '%zx'}], + ['foo=%EF%BF%BD', 'foo=%EF%BF%BD', {'foo': '\ufffd' }] +]; + +// [ wonkyQS, canonicalQS, obj ] +var qsColonTestCases = [ + ['foo:bar', 'foo:bar', {'foo': 'bar'}], + //['foo:bar;foo:quux', 'foo:bar;foo:quux', {'foo': ['bar', 'quux']}], + ['foo:1&bar:2;baz:quux', + 'foo:1%26bar%3A2;baz:quux', + {'foo': '1&bar:2', 'baz': 'quux'}], + ['foo%3Abaz:bar', 'foo%3Abaz:bar', {'foo:baz': 'bar'}], + ['foo:baz:bar', 'foo:baz%3Abar', {'foo': 'baz:bar'}] +]; + +// [wonkyObj, qs, canonicalObj] +var extendedFunction = function() {}; +extendedFunction.prototype = {a: 'b'}; +var qsWeirdObjects = [ + //[{regexp: /./g}, 'regexp=', {'regexp': ''}], + //[{regexp: new RegExp('.', 'g')}, 'regexp=', {'regexp': ''}], + //[{fn: function() {}}, 'fn=', {'fn': ''}], + //[{fn: new Function('')}, 'fn=', {'fn': ''}], + //[{math: Math}, 'math=', {'math': ''}], + //[{e: extendedFunction}, 'e=', {'e': ''}], + //[{d: new Date()}, 'd=', {'d': ''}], + //[{d: Date}, 'd=', {'d': ''}], + //[{f: new Boolean(false), t: new Boolean(true)}, 'f=&t=', {'f': '', 't': ''}], + [{f: false, t: true}, 'f=false&t=true', {'f': 'false', 't': 'true'}], + //[{n: null}, 'n=', {'n': ''}], + //[{nan: NaN}, 'nan=', {'nan': ''}], + //[{inf: Infinity}, 'inf=', {'inf': ''}] +]; +// }}} + +var qsNoMungeTestCases = [ + ['', {}], + //['foo=bar&foo=baz', {'foo': ['bar', 'baz']}], + ['blah=burp', {'blah': 'burp'}], + //['gragh=1&gragh=3&goo=2', {'gragh': ['1', '3'], 'goo': '2'}], + ['frappucino=muffin&goat%5B%5D=scone&pond=moose', + {'frappucino': 'muffin', 'goat[]': 'scone', 'pond': 'moose'}], + ['trololol=yes&lololo=no', {'trololol': 'yes', 'lololo': 'no'}] +]; + +exports['test basic'] = function(assert) { + assert.strictEqual('918854443121279438895193', + qs.parse('id=918854443121279438895193').id, + 'prase id=918854443121279438895193'); +}; + +exports['test that the canonical qs is parsed properly'] = function(assert) { + qsTestCases.forEach(function(testCase) { + assert.deepEqual(testCase[2], qs.parse(testCase[0]), + 'parse ' + testCase[0]); + }); +}; + + +exports['test that the colon test cases can do the same'] = function(assert) { + qsColonTestCases.forEach(function(testCase) { + assert.deepEqual(testCase[2], qs.parse(testCase[0], ';', ':'), + 'parse ' + testCase[0] + ' -> ; :'); + }); +}; + +exports['test the weird objects, that they get parsed properly'] = function(assert) { + qsWeirdObjects.forEach(function(testCase) { + assert.deepEqual(testCase[2], qs.parse(testCase[1]), + 'parse ' + testCase[1]); + }); +}; + +exports['test non munge test cases'] = function(assert) { + qsNoMungeTestCases.forEach(function(testCase) { + //console.log(testCase[0], JSON.stringify(testCase[1]), qs.stringify(testCase[1], '&', '=', false)); + assert.deepEqual(testCase[0], qs.stringify(testCase[1], '&', '=', false), + 'stringify ' + JSON.stringify(testCase[1]) + ' -> & ='); + }); +}; + +exports['test the nested qs-in-qs case'] = function(assert) { + var f = qs.parse('a=b&q=x%3Dy%26y%3Dz'); + f.q = qs.parse(f.q); + assert.deepEqual(f, { a: 'b', q: { x: 'y', y: 'z' } }, + 'parse a=b&q=x%3Dy%26y%3Dz'); +}; + +exports['test nested in colon'] = function(assert) { + var f = qs.parse('a:b;q:x%3Ay%3By%3Az', ';', ':'); + f.q = qs.parse(f.q, ';', ':'); + assert.deepEqual(f, { a: 'b', q: { x: 'y', y: 'z' } }, + 'parse a:b;q:x%3Ay%3By%3Az -> ; :'); +}; + +exports['test stringifying'] = function(assert) { + qsTestCases.forEach(function(testCase) { + assert.equal(testCase[1], qs.stringify(testCase[2]), + 'stringify ' + JSON.stringify(testCase[2])); + }); + + qsColonTestCases.forEach(function(testCase) { + assert.equal(testCase[1], qs.stringify(testCase[2], ';', ':'), + 'stringify ' + JSON.stringify(testCase[2]) + ' -> ; :'); + }); + + qsWeirdObjects.forEach(function(testCase) { + assert.equal(testCase[1], qs.stringify(testCase[0]), + 'stringify ' + JSON.stringify(testCase[0])); + }); +}; + +exports['test stringifying nested'] = function(assert) { + var f = qs.stringify({ + a: 'b', + q: qs.stringify({ + x: 'y', + y: 'z' + }) + }); + assert.equal(f, 'a=b&q=x%3Dy%26y%3Dz', + JSON.stringify({ + a: 'b', + 'qs.stringify -> q': { + x: 'y', + y: 'z' + } + })); + + var threw = false; + try { qs.parse(undefined); } catch(error) { threw = true; } + assert.ok(!threw, "does not throws on undefined"); +}; + +exports['test nested in colon'] = function(assert) { + var f = qs.stringify({ + a: 'b', + q: qs.stringify({ + x: 'y', + y: 'z' + }, ';', ':') + }, ';', ':'); + assert.equal(f, 'a:b;q:x%3Ay%3By%3Az', + 'stringify ' + JSON.stringify({ + a: 'b', + 'qs.stringify -> q': { + x: 'y', + y: 'z' + } + }) + ' -> ; : '); + + + assert.deepEqual({}, qs.parse(), 'parse undefined'); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-registry.js b/tools/addon-sdk-1.12/test/test-registry.js new file mode 100644 index 0000000..13c127b --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-registry.js @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +exports['test:add'] = function(test) { + function Class() {} + let fixture = require('sdk/util/registry').Registry(Class); + let isAddEmitted = false; + fixture.on('add', function(item) { + test.assert( + item instanceof Class, + 'if object added is not an instance should construct instance from it' + ); + test.assert( + fixture.has(item), + 'callback is called after instance is added' + ); + test.assert( + !isAddEmitted, + 'callback should be called for the same item only once' + ); + isAddEmitted = true; + }); + + let object = fixture.add({}); + fixture.add(object); +}; + +exports['test:remove'] = function(test) { + function Class() {} + let fixture = require('sdk/util/registry').Registry(Class); + fixture.on('remove', function(item) { + test.assert( + item instanceof Class, + 'if object removed can be only instance of Class' + ); + test.assert( + fixture.has(item), + 'callback is called before instance is removed' + ); + test.assert( + !isRemoveEmitted, + 'callback should be called for the same item only once' + ); + isRemoveEmitted = true; + }); + + fixture.remove({}); + let object = fixture.add({}); + fixture.remove(object); + fixture.remove(object); +}; + +exports['test:items'] = function(test) { + function Class() {} + let fixture = require('sdk/util/registry').Registry(Class), + actual, + times = 0; + + function testItem(item) { + times ++; + test.assertEqual( + actual, + item, + 'item should match actual item being added/removed' + ); + } + + actual = fixture.add({}); + + fixture.on('add', testItem); + fixture.on('remove', testItem); + + fixture.remove(actual); + fixture.remove(fixture.add(actual = new Class())); + test.assertEqual(3, times, 'should notify listeners on each call'); +} + diff --git a/tools/addon-sdk-1.12/test/test-request.js b/tools/addon-sdk-1.12/test/test-request.js new file mode 100644 index 0000000..8d81ae2 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-request.js @@ -0,0 +1,342 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { Request } = require("sdk/request"); +const { pathFor } = require("sdk/system"); +const { startServerAsync } = require("sdk/test/httpd"); +const file = require("sdk/io/file"); + +const basePath = pathFor("TmpD") +const port = 8099; + + +exports.testOptionsValidator = function(test) { + // First, a simple test to make sure we didn't break normal functionality. + test.assertRaises(function () { + Request({ + url: null + }); + }, 'The option "url" must be one of the following types: string'); + + // Next we'll have a Request that doesn't throw from c'tor, but from a setter. + let req = Request({ + url: "http://playground.zpao.com/jetpack/request/text.php", + onComplete: function () {} + }); + test.assertRaises(function () { + req.url = null; + }, 'The option "url" must be one of the following types: string'); + // The url shouldn't have changed, so check that + test.assertEqual(req.url, "http://playground.zpao.com/jetpack/request/text.php"); +} + +exports.testContentValidator = function(test) { + test.waitUntilDone(); + Request({ + url: "data:text/html;charset=utf-8,response", + content: { 'key1' : null, 'key2' : 'some value' }, + onComplete: function(response) { + test.assertEqual(response.text, "response?key1=null&key2=some+value"); + test.done(); + } + }).get(); +}; + +// All tests below here require a network connection. They will be commented out +// when checked in. If you'd like to run them, simply uncomment them. +// +// When we have the means, these tests will be converted so that they don't +// require an external server nor a network connection. + +// This is a request to a file that exists. +exports.testStatus200 = function (test) { + let srv = startServerAsync(port, basePath); + let content = "Look ma, no hands!\n"; + let basename = "test-request.txt" + prepareFile(basename, content); + + test.waitUntilDone(); + var req = Request({ + url: "http://localhost:" + port + "/" + basename, + onComplete: function (response) { + test.assertEqual(this, req, "`this` should be request"); + test.assertEqual(response.status, 200); + test.assertEqual(response.statusText, "OK"); + test.assertEqual(response.headers["Content-Type"], "text/plain"); + test.assertEqual(response.text, content); + srv.stop(function() test.done()); + } + }).get(); +} + +// This tries to get a file that doesn't exist +exports.testStatus404 = function (test) { + var srv = startServerAsync(port, basePath); + + test.waitUntilDone(); + Request({ + // the following URL doesn't exist + url: "http://localhost:" + port + "/test-request-404.txt", + onComplete: function (response) { + test.assertEqual(response.status, 404); + test.assertEqual(response.statusText, "Not Found"); + srv.stop(function() test.done()); + } + }).get(); +} + +/* +// a simple file with a known header +exports.testKnownHeader = function (test) { + test.waitUntilDone(); + Request({ + url: "http://playground.zpao.com/jetpack/request/headers.php", + onComplete: function (response) { + test.assertEqual(response.headers["x-zpao-header"], "Jamba Juice"); + test.done(); + } + }).get(); +} + +// complex headers +exports.testKnownHeader = function (test) { + let headers = { + "x-zpao-header": "Jamba Juice is: delicious", + "x-zpao-header-2": "foo, bar", + "Set-Cookie": "foo=bar\nbaz=foo" + } + test.waitUntilDone(); + Request({ + url: "http://playground.zpao.com/jetpack/request/complex_headers.php", + onComplete: function (response) { + for (k in headers) { + test.assertEqual(response.headers[k], headers[k]); + } + test.done(); + } + }).get(); +} +*/ + +exports.testSimpleJSON = function (test) { + let srv = startServerAsync(port, basePath); + let json = { foo: "bar" }; + let basename = "test-request.json"; + prepareFile(basename, JSON.stringify(json)); + + test.waitUntilDone(); + Request({ + url: "http://localhost:" + port + "/" + basename, + onComplete: function (response) { + assertDeepEqual(test, response.json, json); + srv.stop(function() test.done()); + } + }).get(); +} + +exports.testInvalidJSON = function (test) { + let srv = startServerAsync(port, basePath); + let basename = "test-request-invalid.json"; + prepareFile(basename, '"this": "isn\'t JSON"'); + + test.waitUntilDone(); + Request({ + url: "http://localhost:" + port + "/" + basename, + onComplete: function (response) { + test.assertEqual(response.json, null); + srv.stop(function() test.done()); + } + }).get(); +} + +/* +exports.testGetWithParamsNotContent = function (test) { + test.waitUntilDone(); + Request({ + url: "http://playground.zpao.com/jetpack/request/getpost.php?foo=bar", + onComplete: function (response) { + let expected = { + "POST": [], + "GET" : { foo: "bar" } + }; + assertDeepEqual(test, response.json, expected); + test.done(); + } + }).get(); +} + +exports.testGetWithContent = function (test) { + test.waitUntilDone(); + Request({ + url: "http://playground.zpao.com/jetpack/request/getpost.php", + content: { foo: "bar" }, + onComplete: function (response) { + let expected = { + "POST": [], + "GET" : { foo: "bar" } + }; + assertDeepEqual(test, response.json, expected); + test.done(); + } + }).get(); +} + +exports.testGetWithParamsAndContent = function (test) { + test.waitUntilDone(); + Request({ + url: "http://playground.zpao.com/jetpack/request/getpost.php?foo=bar", + content: { baz: "foo" }, + onComplete: function (response) { + let expected = { + "POST": [], + "GET" : { foo: "bar", baz: "foo" } + }; + assertDeepEqual(test, response.json, expected); + test.done(); + } + }).get(); +} + +exports.testSimplePost = function (test) { + test.waitUntilDone(); + Request({ + url: "http://playground.zpao.com/jetpack/request/getpost.php", + content: { foo: "bar" }, + onComplete: function (response) { + let expected = { + "POST": { foo: "bar" }, + "GET" : [] + }; + assertDeepEqual(test, response.json, expected); + test.done(); + } + }).post(); +} + +exports.testEncodedContent = function (test) { + test.waitUntilDone(); + Request({ + url: "http://playground.zpao.com/jetpack/request/getpost.php", + content: "foo=bar&baz=foo", + onComplete: function (response) { + let expected = { + "POST": [], + "GET" : { foo: "bar", baz: "foo" } + }; + assertDeepEqual(test, response.json, expected); + test.done(); + } + }).get(); +} + +exports.testEncodedContentWithSpaces = function (test) { + test.waitUntilDone(); + Request({ + url: "http://playground.zpao.com/jetpack/request/getpost.php", + content: "foo=bar+hop!&baz=foo", + onComplete: function (response) { + let expected = { + "POST": [], + "GET" : { foo: "bar hop!", baz: "foo" } + }; + assertDeepEqual(test, response.json, expected); + test.done(); + } + }).get(); +} + +exports.testGetWithArray = function (test) { + test.waitUntilDone(); + Request({ + url: "http://playground.zpao.com/jetpack/request/getpost.php", + content: { foo: [1, 2], baz: "foo" }, + onComplete: function (response) { + let expected = { + "POST": [], + "GET" : { foo: [1, 2], baz: "foo" } + }; + assertDeepEqual(test, response.json, expected); + test.done(); + } + }).get(); +} + +exports.testGetWithNestedArray = function (test) { + test.waitUntilDone(); + Request({ + url: "http://playground.zpao.com/jetpack/request/getpost.php", + content: { foo: [1, 2, [3, 4]], bar: "baz" }, + onComplete: function (response) { + let expected = { + "POST": [], + "GET" : this.content + }; + assertDeepEqual(test, response.json, expected); + test.done(); + } + }).get(); +} + +exports.testGetWithNestedArray = function (test) { + test.waitUntilDone(); + let request = Request({ + url: "http://playground.zpao.com/jetpack/request/getpost.php", + content: { + foo: [1, 2, { + omg: "bbq", + "all your base!": "are belong to us" + }], + bar: "baz" + }, + onComplete: function (response) { + let expected = { + "POST": [], + "GET" : request.content + }; + assertDeepEqual(test, response.json, expected); + test.done(); + } + }).get(); +} +*/ + +// This is not a proper testing for deep equal, but it's good enough for my uses +// here. It will do type coercion to check equality, but that's good here. Data +// coming from the server will be stringified and so "0" should be equal to 0. +function assertDeepEqual(test, obj1, obj2, msg) { + function equal(o1, o2) { + // cover our non-object cases well enough + if (o1 == o2) + return true; + if (typeof(o1) != typeof(o2)) + return false; + if (typeof(o1) != "object") + return o1 == o2; + + let e = true; + for (let key in o1) { + let val = o1[key]; + e = e && key in o2 && equal(o2[key], val); + if (!e) + break; + } + for (let key in o2) { + let val = o2[key] + e = e && key in o1 && equal(o1[key], val); + if (!e) + break; + } + return e; + } + msg = msg || "objects not equal - " + JSON.stringify(obj1) + " != " + + JSON.stringify(obj2); + test.assert(equal(obj1, obj2), msg); +} + +function prepareFile(basename, content) { + let filePath = file.join(basePath, basename); + let fileStream = file.open(filePath, 'w'); + fileStream.write(content); + fileStream.close(); +} diff --git a/tools/addon-sdk-1.12/test/test-require.js b/tools/addon-sdk-1.12/test/test-require.js new file mode 100644 index 0000000..d7e790e --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-require.js @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const traceback = require("sdk/console/traceback"); + +exports.test_no_args = function(test) { + var passed = false; + try { + var oops = require(); // leave this on line 6! + } catch(e) { + let msg = e.toString(); + test.assertEqual(msg.indexOf("Error: you must provide a module name when calling require() from "), 0); + test.assertNotEqual(msg.indexOf("test-require"), -1, msg); + // we'd also like to assert that the right filename and linenumber is in + // the stack trace, but this currently doesn't work (see bugs 679591 and + // 551604) + if (0) { + let tb = traceback.fromException(e); + let lastFrame = tb[tb.length-1]; + test.assertNotEqual(lastFrame.filename.indexOf("test-require.js"), -1, + lastFrame.filename); + test.assertEqual(lastFrame.lineNo, 6); + test.assertEqual(lastFrame.funcName, "??"); + } + passed = true; + } + test.assert(passed, 'require() with no args should raise helpful error'); +}; diff --git a/tools/addon-sdk-1.12/test/test-sandbox.js b/tools/addon-sdk-1.12/test/test-sandbox.js new file mode 100644 index 0000000..4f2de96 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-sandbox.js @@ -0,0 +1,121 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { sandbox, load, evaluate } = require('sdk/loader/sandbox'); +const xulApp = require("sdk/system/xul-app"); +const fixturesURI = module.uri.split('test-sandbox.js')[0] + 'fixtures/'; + + +exports['test basics'] = function(assert) { + let fixture = sandbox('http://example.com'); + assert.equal(evaluate(fixture, 'var a = 1;'), undefined, + 'returns expression value'); + assert.equal(evaluate(fixture, 'b = 2;'), 2, + 'returns expression value'); + assert.equal(fixture.b, 2, 'global is defined as property'); + assert.equal(fixture.a, 1, 'global is defined as property'); + assert.equal(evaluate(fixture, 'a + b;'), 3, 'returns correct sum'); +}; + +exports['test non-privileged'] = function(assert) { + let fixture = sandbox('http://example.com'); + if (xulApp.versionInRange(xulApp.platformVersion, "15.0a1", "18.*")) { + let rv = evaluate(fixture, 'Compo' + 'nents.utils'); + assert.equal(rv, undefined, + "Components's attributes are undefined in content sandboxes"); + } + else { + assert.throws(function() { + evaluate(fixture, 'Compo' + 'nents.utils'); + }, 'Access to components is restricted'); + } + fixture.sandbox = sandbox; + assert.throws(function() { + evaluate(fixture, sandbox('http://foo.com')); + }, 'Can not call privileged code'); +}; + +exports['test injection'] = function(assert) { + let fixture = sandbox(); + fixture.hi = function(name) 'Hi ' + name + assert.equal(evaluate(fixture, 'hi("sandbox");'), 'Hi sandbox', + 'injected functions are callable'); +}; + +exports['test exceptions'] = function(assert) { + let fixture = sandbox(); + try { + evaluate(fixture, '!' + function() { + var message = 'boom'; + throw Error(message); + } + '();'); + } + catch (error) { + assert.equal(error.fileName, '', 'no fileName reported'); + assert.equal(error.lineNumber, 3, 'reports correct line number'); + } + + try { + evaluate(fixture, '!' + function() { + var message = 'boom'; + throw Error(message); + } + '();', 'foo.js'); + } + catch (error) { + assert.equal(error.fileName, 'foo.js', 'correct fileName reported'); + assert.equal(error.lineNumber, 3, 'reports correct line number'); + } + + try { + evaluate(fixture, '!' + function() { + var message = 'boom'; + throw Error(message); + } + '();', 'foo.js', 2); + } + catch (error) { + assert.equal(error.fileName, 'foo.js', 'correct fileName reported'); + assert.equal(error.lineNumber, 4, 'line number was opted'); + } +}; + +exports['test opt version'] = function(assert) { + let fixture = sandbox(); + assert.throws(function() { + evaluate(fixture, 'let a = 2;', 'test.js', 1, '1.5'); + }, 'No let in js 1.5'); +}; + +exports['test load'] = function(assert) { + let fixture = sandbox(); + load(fixture, fixturesURI + 'sandbox-normal.js'); + assert.equal(fixture.a, 1, 'global variable defined'); + assert.equal(fixture.b, 2, 'global via `this` property was set'); + assert.equal(fixture.f(), 4, 'function was defined'); +}; + +exports['test load with data: URL'] = function(assert) { + let code = "var a = 1; this.b = 2; function f() 4"; + let fixture = sandbox(); + load(fixture, "data:," + encodeURIComponent(code)); + + assert.equal(fixture.a, 1, 'global variable defined'); + assert.equal(fixture.b, 2, 'global via `this` property was set'); + assert.equal(fixture.f(), 4, 'function was defined'); +}; + +exports['test load script with complex char'] = function(assert) { + let fixture = sandbox(); + load(fixture, fixturesURI + 'sandbox-complex-character.js'); + assert.equal(fixture.chars, 'გამარჯობა', 'complex chars were loaded correctly'); +}; + +exports['test load script with data: URL and complex char'] = function(assert) { + let code = "var chars = 'გამარჯობა';"; + let fixture = sandbox(); + load(fixture, "data:," + encodeURIComponent(code)); + + assert.equal(fixture.chars, 'გამარჯობა', 'complex chars were loaded correctly'); +}; + +require('test').run(exports); diff --git a/tools/addon-sdk-1.12/test/test-selection.js b/tools/addon-sdk-1.12/test/test-selection.js new file mode 100644 index 0000000..9985ca4 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-selection.js @@ -0,0 +1,457 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +let timer = require("sdk/timers"); +let {Cc,Ci} = require("chrome"); + +// Arbitrary delay needed to avoid weird behavior. +// TODO: We need to find all uses of this and replace them +// with more deterministic solutions. +const ARB_DELAY = 100; + +// Select all divs elements in an HTML document +function selectAllDivs(window) { + let divs = window.document.getElementsByTagName("div"); + let s = window.getSelection(); + if (s.rangeCount > 0) + s.removeAllRanges(); + for (let i = 0; i < divs.length; i++) { + let range = window.document.createRange(); + range.selectNode(divs[i]); + s.addRange(range); + } +} + +function selectTextarea(window, from, to) { + let textarea = window.document.getElementsByTagName("textarea")[0]; + + from = from || 0; + to = to || textarea.value.length; + + textarea.setSelectionRange(from, to); + textarea.focus(); +} + +function primeTestCase(html, test, callback) { + let tabBrowser = require("sdk/deprecated/tab-browser"); + let dataURL = "data:text/html;charset=utf-8," + encodeURI(html); + let tracker = tabBrowser.whenContentLoaded( + function(window) { + if (window.document.location.href != dataURL) + return; + callback(window, test); + timer.setTimeout(function() { + tracker.unload(); + test.done(); + window.close(); + }, + ARB_DELAY); + } + ); + tabBrowser.addTab(dataURL); +} + +const DIV1 = '<div id="foo">bar</div>'; +const DIV2 = '<div>noodles</div>'; +const HTML_MULTIPLE = '<html><body>' + DIV1 + DIV2 + '</body></html>'; +const HTML_SINGLE = '<html><body>' + DIV1 + '</body></html>'; + +// Tests of contiguous + +exports.testContiguousMultiple = function testContiguousMultiple(test) { + let selection = require("sdk/selection"); + primeTestCase(HTML_MULTIPLE, test, function(window, test) { + selectAllDivs(window); + test.assertEqual(selection.isContiguous, false, + "selection.isContiguous multiple works."); + }); + + test.waitUntilDone(5000); +}; + +exports.testContiguousSingle = function testContiguousSingle(test) { + let selection = require("sdk/selection"); + primeTestCase(HTML_SINGLE, test, function(window, test) { + selectAllDivs(window); + test.assertEqual(selection.isContiguous, true, + "selection.isContiguous single works."); + }); + + test.waitUntilDone(5000); +}; + +exports.testContiguousWithoutSelection = + function testContiguousWithoutSelection(test) { + let selection = require("sdk/selection"); + primeTestCase(HTML_SINGLE, test, function(window, test) { + test.assertEqual(selection.isContiguous, false, + "selection.isContiguous without selection works."); + }); + + test.waitUntilDone(5000); +}; + +/** + * Test that setting the contiguous property has no effect. + */ +/*exports.testSetContiguous = function testSetContiguous(test) { + let selection = require("sdk/selection"); + primeTestCase(HTML_MULTIPLE, test, function(window, test) { + selectAllDivs(window); + try { + selection.isContiguous = true; + test.assertEqual(selection.isContiguous, false, + "setting selection.isContiguous doesn't work (as expected)."); + } + catch (e) { + test.pass("setting selection.isContiguous doesn't work (as expected)."); + } + }); + + test.waitUntilDone(5000); +};*/ + + +// HTML tests + +exports.testGetHTMLSingleSelection = function testGetHTMLSingleSelection(test) { + let selection = require("sdk/selection"); + primeTestCase(HTML_SINGLE, test, function(window, test) { + selectAllDivs(window); + test.assertEqual(selection.html, DIV1, "get html selection works"); + }); + + test.waitUntilDone(5000); +}; + +/* Myk's comments: This is fine. However, it reminds me to figure out and + specify whether iteration is ordered. If so, we'll want to change this + test in the future to test that the discontiguous selections are returned in + the appropriate order. In the meantime, add a comment to that effect here */ +exports.testGetHTMLMultipleSelection = + function testGetHTMLMultipleSelection(test) { + let selection = require("sdk/selection"); + primeTestCase(HTML_MULTIPLE, test, function(window, test) { + selectAllDivs(window); + let assertions = false; + for each (let i in selection) { + test.assertEqual(true, [DIV1, DIV2].some(function(t) t == i.html), + "get multiple selection html works"); + assertions = true; + } + // Ensure we ran at least one assertEqual() + test.assert(assertions, "No assertions were called"); + }); + + test.waitUntilDone(5000); +}; + +exports.testGetHTMLNull = function testGetHTMLNull(test) { + let selection = require("sdk/selection"); + primeTestCase(HTML_SINGLE, test, function(window, test) { + test.assertEqual(selection.html, null, "get html null works"); + }); + + test.waitUntilDone(5000); +}; + +exports.testGetHTMLWeird = function testGetHTMLWeird(test) { + let selection = require("sdk/selection"); + // If the getter is used when there are contiguous selections, the first + // selection should be returned + primeTestCase(HTML_MULTIPLE, test, function(window, test) { + selectAllDivs(window); + test.assertEqual(selection.html, DIV1, "get html weird works"); + }); + + test.waitUntilDone(5000); +}; + +exports.testGetHTMLNullInTextareaSelection = + function testGetHTMLNullInTextareaSelection(test) { + let selection = require("sdk/selection"); + + primeTestCase(TEXT_FIELD, test, function(window, test) { + selectTextarea(window); + + test.assertEqual(selection.html, null, "get html null in textarea works") + }); + + test.waitUntilDone(5000); +}; + +const REPLACEMENT_HTML = "<b>Lorem ipsum dolor sit amet</b>"; + +exports.testSetHTMLSelection = function testSetHTMLSelection(test) { + let selection = require("sdk/selection"); + primeTestCase(HTML_SINGLE, test, function(window, test) { + selectAllDivs(window); + selection.html = REPLACEMENT_HTML; + test.assertEqual(selection.html, "<span>" + REPLACEMENT_HTML + + "</span>", "selection html works"); + }); + + test.waitUntilDone(5000); +}; + +exports.testSetHTMLException = function testSetHTMLException(test) { + let selection = require("sdk/selection"); + primeTestCase(HTML_SINGLE, test, function(window, test) { + try { + selection.html = REPLACEMENT_HTML; + test.fail("set HTML throws when there's no selection."); + } + catch (e) { + test.pass("set HTML throws when there's no selection."); + } + }); + + test.waitUntilDone(5000); +}; + +const TEXT1 = "foo"; +const TEXT2 = "noodles"; +const TEXT_MULTIPLE = "<html><body><div>" + TEXT1 + "</div><div>" + TEXT2 + + "</div></body></html>"; +const TEXT_SINGLE = "<html><body><div>" + TEXT1 + "</div></body></html>"; +const TEXT_FIELD = "<html><body><textarea>" + TEXT1 + "</textarea></body></html>"; + +// Text tests + +exports.testSetHTMLinTextareaSelection = + function testSetHTMLinTextareaSelection(test) { + let selection = require("sdk/selection"); + + primeTestCase(TEXT_FIELD, test, function(window, test) { + selectTextarea(window); + + // HTML string is set as plain text in textareas, that's because + // `selection.html` and `selection.text` are basically aliases when a + // value is set. See bug 677269 + selection.html = REPLACEMENT_HTML; + + test.assertEqual(selection.text, REPLACEMENT_HTML, + "set selection html in textarea works"); + }); + + test.waitUntilDone(5000); +}; + +exports.testGetTextSingleSelection = + function testGetTextSingleSelection(test) { + let selection = require("sdk/selection"); + primeTestCase(TEXT_SINGLE, test, function(window, test) { + selectAllDivs(window); + test.assertEqual(selection.text, TEXT1, "get text selection works"); + }); + + test.waitUntilDone(5000); +}; + +exports.testGetTextMultipleSelection = + function testGetTextMultipleSelection(test) { + let selection = require("sdk/selection"); + primeTestCase(TEXT_MULTIPLE, test, function(window, test) { + selectAllDivs(window); + let assertions = false; + for each (let i in selection) { + test.assertEqual(true, [TEXT1, TEXT2].some(function(t) t == i.text), + "get multiple selection text works"); + assertions = true; + } + // Ensure we ran at least one assertEqual() + test.assert(assertions, "No assertions were called"); + }); + + test.waitUntilDone(5000); +}; + +exports.testGetTextNull = function testGetTextNull(test) { + let selection = require("sdk/selection"); + primeTestCase(TEXT_SINGLE, test, function(window, test) { + test.assertEqual(selection.text, null, "get text null works"); + }); + + test.waitUntilDone(5000); +}; + +exports.testGetTextWeird = function testGetTextWeird(test) { + let selection = require("sdk/selection"); + // If the getter is used when there are contiguous selections, the first + // selection should be returned + primeTestCase(TEXT_MULTIPLE, test, function(window, test) { + selectAllDivs(window); + test.assertEqual(selection.text, TEXT1, "get text weird works"); + }); + + test.waitUntilDone(5000); +}; + +exports.testGetTextNullInTextareaSelection = + function testGetTextInTextareaSelection(test) { + let selection = require("sdk/selection"); + + primeTestCase(TEXT_FIELD, test, function(window, test) { + test.assertEqual(selection.text, null, "get text null in textarea works") + }); + + test.waitUntilDone(5000); +}; + +exports.testGetTextInTextareaSelection = + function testGetTextInTextareaSelection(test) { + let selection = require("sdk/selection"); + + primeTestCase(TEXT_FIELD, test, function(window, test) { + selectTextarea(window); + + test.assertEqual(selection.text, TEXT1, "get text null in textarea works") + }); + + test.waitUntilDone(5000); +}; + +const REPLACEMENT_TEXT = "Lorem ipsum dolor sit amet"; + +exports.testSetTextSelection = function testSetTextSelection(test) { + let selection = require("sdk/selection"); + primeTestCase(TEXT_SINGLE, test, function(window, test) { + selectAllDivs(window); + selection.text = REPLACEMENT_TEXT; + test.assertEqual(selection.text, REPLACEMENT_TEXT, "selection text works"); + }); + + test.waitUntilDone(5000); +}; + +exports.testSetHTMLException = function testSetHTMLException(test) { + let selection = require("sdk/selection"); + primeTestCase(TEXT_SINGLE, test, function(window, test) { + try { + selection.text = REPLACEMENT_TEXT; + test.fail("set HTML throws when there's no selection."); + } + catch (e) { + test.pass("set HTML throws when there's no selection."); + } + }); + + test.waitUntilDone(5000); +}; + +exports.testSetTextInTextareaSelection = + function testSetTextInTextareaSelection(test) { + let selection = require("sdk/selection"); + + primeTestCase(TEXT_FIELD, test, function(window, test) { + selectTextarea(window); + + selection.text = REPLACEMENT_TEXT; + + test.assertEqual(selection.text, REPLACEMENT_TEXT, + "set selection text in textarea works"); + }); + + test.waitUntilDone(5000); +}; + +// Iterator tests + +exports.testIterator = function testIterator(test) { + let selection = require("sdk/selection"); + let selectionCount = 0; + primeTestCase(TEXT_MULTIPLE, test, function(window, test) { + selectAllDivs(window); + for each (let i in selection) + selectionCount++; + test.assertEqual(2, selectionCount, "iterator works."); + }); + + test.waitUntilDone(5000); +}; + +exports.testIteratorWithTextareaSelection = + function testIteratorWithTextareaSelection(test) { + let selection = require("sdk/selection"); + let selectionCount = 0; + + primeTestCase(TEXT_FIELD, test, function(window, test) { + selectTextarea(window); + + for each (let i in selection) + selectionCount++; + + test.assertEqual(1, selectionCount, "iterator works in textarea."); + }); + + test.waitUntilDone(5000); +}; + +/* onSelect tests */ + +/* +function sendSelectionSetEvent(window) { + const Ci = Components['interfaces']; + let utils = window.QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDOMWindowUtils); + if (!utils.sendSelectionSetEvent(0, 1, false)) + dump("**** sendSelectionSetEvent did not select anything\n"); + else + dump("**** sendSelectionSetEvent succeeded\n"); +} + +// testOnSelect() requires nsIDOMWindowUtils, which is only available in +// Firefox 3.7+. +exports.testOnSelect = function testOnSelect(test) { + let selection = require("sdk/selection"); + let callbackCount = 0; + primeTestCase(TEXT_SINGLE, test, function(window, test) { + selection.onSelect = function() {callbackCount++}; + // Now simulate the user selecting stuff + sendSelectionSetEvent(window); + selection.text = REPLACEMENT_TEXT; + test.assertEqual(1, callbackCount, "onSelect text listener works."); + //test.pass(); + //test.done(); + }); + + test.waitUntilDone(5000); +}; + +// testOnSelectExceptionNoBubble() requires nsIDOMWindowUtils, which is only +// available in Firefox 3.7+. +exports.testOnSelectExceptionNoBubble = + function testOnSelectTextSelection(test) { + let selection = require("sdk/selection"); + primeTestCase(HTML_SINGLE, test, function(window, test) { + selection.onSelect = function() { + throw new Error("Exception thrown in testOnSelectExceptionNoBubble"); + }; + // Now simulate the user selecting stuff + sendSelectionSetEvent(window); + test.pass("onSelect catches exceptions."); + }); + + test.waitUntilDone(5000); +}; +*/ + +// If the module doesn't support the app we're being run in, require() will +// throw. In that case, remove all tests above from exports, and add one dummy +// test that passes. +try { + require("sdk/selection"); +} +catch (err) { + // This bug should be mentioned in the error message. + let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=560716"; + if (err.message.indexOf(bug) < 0) + throw err; + + module.exports = { + testAppNotSupported: function (test) { + test.pass("the selection module does not support this application."); + } + } +} diff --git a/tools/addon-sdk-1.12/test/test-self.js b/tools/addon-sdk-1.12/test/test-self.js new file mode 100644 index 0000000..6a4b530 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-self.js @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const {Cc, Ci, Cu, Cm, components} = require('chrome'); +Cu.import("resource://gre/modules/AddonManager.jsm", this); + +exports.testSelf = function(test) { + var self = require("sdk/self"); + + var source = self.data.load("test-content-symbiont.js"); + test.assert(source.match(/test-content-symbiont/), "self.data.load() works"); + + // Likewise, we can't assert anything about the full URL, because that + // depends on self.id . We can only assert that it ends in the right + // thing. + var url = self.data.url("test-content-symbiont.js"); + test.assertEqual(typeof(url), "string", "self.data.url('x') returns string"); + test.assertEqual(/\/test-content-symbiont\.js$/.test(url), true); + + // Make sure 'undefined' is not in url when no string is provided. + url = self.data.url(); + test.assertEqual(typeof(url), "string", "self.data.url() returns string"); + test.assertEqual(/\/undefined$/.test(url), false); + + // When tests are run on just the api-utils package, self.name is + // api-utils. When they're run as 'cfx testall', self.name is testpkgs. + test.assert(self.name == "addon-sdk", "self.name is addon-sdk"); + + // loadReason may change here, as we change the way tests addons are installed + test.assertEqual(self.loadReason, "startup", + "self.loadReason is always `startup` on test runs"); +}; + +exports.testSelfID = function(test) { + test.waitUntilDone(); + + var self = require("sdk/self"); + // We can't assert anything about the ID inside the unit test right now, + // because the ID we get depends upon how the test was invoked. The idea + // is that it is supposed to come from the main top-level package's + // package.json file, from the "id" key. + test.assertEqual(typeof(self.id), "string", "self.id is a string"); + test.assert(self.id.length > 0); + + AddonManager.getAddonByID(self.id, function(addon) { + if (!addon) { + test.fail("did not find addon with self.id"); + } + else { + test.pass("found addon with self.id"); + } + test.done(); + }); +} diff --git a/tools/addon-sdk-1.12/test/test-set-exports.js b/tools/addon-sdk-1.12/test/test-set-exports.js new file mode 100644 index 0000000..5221237 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-set-exports.js @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +let four = require("./modules/exportsEquals"); +exports.testExportsEquals = function(test) { + test.assertEqual(four, 4); +} + +/* TODO: Discuss idea of dropping support for this feature that was alternative + to `module.exports = ..` that failed. +let five = require("./modules/setExports"); +exports.testSetExports = function(test) { + test.assertEqual(five, 5); +} + +exports.testDupeSetExports = function(test) { + var passed = false; + try { + var dupe = require('./modules/dupeSetExports'); + } catch(e) { + passed = /define\(\) was used, so module\.exports= and module\.setExports\(\) may not be used/.test(e.toString()); + } + test.assertEqual(passed, true, 'define() or setExports(), not both'); +} +*/ + +exports.testModule = function(test) { + // module.id is not cast in stone yet. In the future, it may include the + // package name, or may possibly be a/ URL of some sort. For now, it's a + // URL that starts with resource: and ends with this module name, but the + // part in between varies depending upon how the test is run. + var found = /test-set-exports$/.test(module.id); + test.assertEqual(found, true, module.id+" ends with test-set-exports.js"); +} diff --git a/tools/addon-sdk-1.12/test/test-simple-prefs.js b/tools/addon-sdk-1.12/test/test-simple-prefs.js new file mode 100644 index 0000000..920af33 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-simple-prefs.js @@ -0,0 +1,231 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const { Loader } = require("sdk/test/loader"); +const { setTimeout } = require("sdk/timers"); +const { notify } = require("sdk/deprecated/observer-service"); +const { id } = require("sdk/self"); +const simplePrefs = require("sdk/simple-prefs"); +const { prefs: sp } = simplePrefs; + +const specialChars = "!@#$%^&*()_-=+[]{}~`\'\"<>,./?;:"; + +exports.testIterations = function(test) { + sp["test"] = true; + sp["test.test"] = true; + let prefAry = []; + for (var name in sp ) { + prefAry.push(name); + } + test.assert("test" in sp); + test.assert(!sp.getPropertyDescriptor); + test.assert(Object.prototype.hasOwnProperty.call(sp, "test")); + test.assertEqual(["test", "test.test"].toString(), prefAry.sort().toString(), "for (x in y) part 1/2 works"); + test.assertEqual(["test", "test.test"].toString(), Object.keys(sp).sort().toString(), "Object.keys works"); + + delete sp["test"]; + delete sp["test.test"]; + let prefAry = []; + for (var name in sp ) { + prefAry.push(name); + } + test.assertEqual([].toString(), prefAry.toString(), "for (x in y) part 2/2 works"); +} + +exports.testSetGetBool = function(test) { + test.assertEqual(sp.test, undefined, "Value should not exist"); + sp.test = true; + test.assert(sp.test, "Value read should be the value previously set"); +}; + +// TEST: setting and getting preferences with special characters work +exports.testSpecialChars = function(test) { + let chars = specialChars.split(""); + let len = chars.length; + + let count = 0; + chars.forEach(function(char) { + let rand = Math.random() + ""; + simplePrefs.on(char, function onPrefChanged() { + simplePrefs.removeListener(char, onPrefChanged); + test.assertEqual(sp[char], rand, "setting pref with a name that is a special char, " + char + ", worked!"); + + // end test + if (++count == len) + test.done(); + }) + sp[char] = rand; + }); +}; + +exports.testSetGetInt = function(test) { + test.assertEqual(sp["test-int"], undefined, "Value should not exist"); + sp["test-int"] = 1; + test.assertEqual(sp["test-int"], 1, "Value read should be the value previously set"); +}; + +exports.testSetComplex = function(test) { + try { + sp["test-complex"] = {test: true}; + test.fail("Complex values are not allowed"); + } + catch (e) { + test.pass("Complex values are not allowed"); + } +}; + +exports.testSetGetString = function(test) { + test.assertEqual(sp["test-string"], undefined, "Value should not exist"); + sp["test-string"] = "test"; + test.assertEqual(sp["test-string"], "test", "Value read should be the value previously set"); +}; + +exports.testHasAndRemove = function(test) { + sp.test = true; + test.assert(("test" in sp), "Value exists"); + delete sp.test; + test.assertEqual(sp.test, undefined, "Value should be undefined"); +}; + +exports.testPrefListener = function(test) { + test.waitUntilDone(); + + let listener = function(prefName) { + simplePrefs.removeListener('test-listener', listener); + test.assertEqual(prefName, "test-listen", "The prefs listener heard the right event"); + test.done(); + }; + + simplePrefs.on("test-listen", listener); + + sp["test-listen"] = true; + + // Wildcard listen + let toSet = ['wildcard1','wildcard.pref2','wildcard.even.longer.test']; + let observed = []; + + let wildlistener = function(prefName) { + if (toSet.indexOf(prefName) > -1) observed.push(prefName); + }; + + simplePrefs.on('',wildlistener); + + toSet.forEach(function(pref) { + sp[pref] = true; + }); + + test.assert((observed.length == 3 && toSet.length == 3), + "Wildcard lengths inconsistent" + JSON.stringify([observed.length, toSet.length])); + + toSet.forEach(function(pref,ii) { + test.assertEqual(observed[ii], pref, "Wildcard observed " + pref); + }); + + simplePrefs.removeListener('',wildlistener); + +}; + +exports.testBtnListener = function(test) { + test.waitUntilDone(); + + let name = "test-btn-listen"; + simplePrefs.on(name, function listener() { + simplePrefs.removeListener(name, listener); + test.pass("Button press event was heard"); + test.done(); + }); + notify((id + "-cmdPressed"), "", name); +}; + +exports.testPrefRemoveListener = function(test) { + test.waitUntilDone(); + + let counter = 0; + + let listener = function() { + test.pass("The prefs listener was not removed yet"); + + if (++counter > 1) + test.fail("The prefs listener was not removed"); + + simplePrefs.removeListener("test-listen2", listener); + + sp["test-listen2"] = false; + + setTimeout(function() { + test.pass("The prefs listener was removed"); + test.done(); + }, 250); + }; + + simplePrefs.on("test-listen2", listener); + + // emit change + sp["test-listen2"] = true; +}; + +// Bug 710117: Test that simple-pref listeners are removed on unload +exports.testPrefUnloadListener = function(test) { + test.waitUntilDone(); + + let loader = Loader(module); + let sp = loader.require("sdk/simple-prefs"); + let counter = 0; + + let listener = function() { + test.assertEqual(++counter, 1, "This listener should only be called once"); + + loader.unload(); + + // this may not execute after unload, but definitely shouldn't fire listener + sp.prefs["test-listen3"] = false; + // this should execute, but also definitely shouldn't fire listener + require("sdk/simple-prefs").prefs["test-listen3"] = false; + + test.done(); + }; + + sp.on("test-listen3", listener); + + // emit change + sp.prefs["test-listen3"] = true; +}; + + +// Bug 710117: Test that simple-pref listeners are removed on unload +exports.testPrefUnloadWildcardListener = function(test) { + test.waitUntilDone(); + let testpref = "test-wildcard-unload-listener"; + let loader = Loader(module); + let sp = loader.require("simple-prefs"); + let counter = 0; + + let listener = function() { + test.assertEqual(++counter, 1, "This listener should only be called once"); + + loader.unload(); + + // this may not execute after unload, but definitely shouldn't fire listener + sp.prefs[testpref] = false; + // this should execute, but also definitely shouldn't fire listener + require("simple-prefs").prefs[testpref] = false; + + test.done(); + }; + + sp.on("", listener); + // emit change + sp.prefs[testpref] = true; +}; + + +// Bug 732919 - JSON.stringify() fails on simple-prefs.prefs +exports.testPrefJSONStringification = function(test) { + var sp = require("sdk/simple-prefs").prefs; + test.assertEqual( + Object.keys(sp).join(), + Object.keys(JSON.parse(JSON.stringify(sp))).join(), + "JSON stringification should work."); +}; diff --git a/tools/addon-sdk-1.12/test/test-simple-storage.js b/tools/addon-sdk-1.12/test/test-simple-storage.js new file mode 100644 index 0000000..2202bed --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-simple-storage.js @@ -0,0 +1,311 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim:set ts=2 sw=2 sts=2 et filetype=javascript + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const file = require("sdk/io/file"); +const prefs = require("sdk/preferences/service"); + +const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota"; + +let {Cc,Ci} = require("chrome"); + +const { Loader } = require("sdk/test/loader"); +const { id } = require("sdk/self"); + +let storeFile = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties). + get("ProfD", Ci.nsIFile); +storeFile.append("jetpack"); +storeFile.append(id); +storeFile.append("simple-storage"); +storeFile.append("store.json"); +let storeFilename = storeFile.path; + +function manager(loader) loader.sandbox("sdk/simple-storage").manager; + +exports.testSetGet = function (test) { + test.waitUntilDone(); + + // Load the module once, set a value. + let loader = Loader(module); + let ss = loader.require("sdk/simple-storage"); + manager(loader).jsonStore.onWrite = function (storage) { + test.assert(file.exists(storeFilename), "Store file should exist"); + + // Load the module again and make sure the value stuck. + loader = Loader(module); + ss = loader.require("sdk/simple-storage"); + manager(loader).jsonStore.onWrite = function (storage) { + file.remove(storeFilename); + test.done(); + }; + test.assertEqual(ss.storage.foo, val, "Value should persist"); + loader.unload(); + }; + let val = "foo"; + ss.storage.foo = val; + test.assertEqual(ss.storage.foo, val, "Value read should be value set"); + loader.unload(); +}; + +exports.testSetGetRootArray = function (test) { + setGetRoot(test, [1, 2, 3], function (arr1, arr2) { + if (arr1.length !== arr2.length) + return false; + for (let i = 0; i < arr1.length; i++) { + if (arr1[i] !== arr2[i]) + return false; + } + return true; + }); +}; + +exports.testSetGetRootBool = function (test) { + setGetRoot(test, true); +}; + +exports.testSetGetRootFunction = function (test) { + setGetRootError(test, function () {}, + "Setting storage to a function should fail"); +}; + +exports.testSetGetRootNull = function (test) { + setGetRoot(test, null); +}; + +exports.testSetGetRootNumber = function (test) { + setGetRoot(test, 3.14); +}; + +exports.testSetGetRootObject = function (test) { + setGetRoot(test, { foo: 1, bar: 2 }, function (obj1, obj2) { + for (let prop in obj1) { + if (!(prop in obj2) || obj2[prop] !== obj1[prop]) + return false; + } + for (let prop in obj2) { + if (!(prop in obj1) || obj1[prop] !== obj2[prop]) + return false; + } + return true; + }); +}; + +exports.testSetGetRootString = function (test) { + setGetRoot(test, "sho' 'nuff"); +}; + +exports.testSetGetRootUndefined = function (test) { + setGetRootError(test, undefined, "Setting storage to undefined should fail"); +}; + +exports.testEmpty = function (test) { + let loader = Loader(module); + let ss = loader.require("sdk/simple-storage"); + loader.unload(); + test.assert(!file.exists(storeFilename), "Store file should not exist"); +}; + +exports.testMalformed = function (test) { + let stream = file.open(storeFilename, "w"); + stream.write("i'm not json"); + stream.close(); + let loader = Loader(module); + let ss = loader.require("sdk/simple-storage"); + let empty = true; + for (let key in ss.storage) { + empty = false; + break; + } + test.assert(empty, "Malformed storage should cause root to be empty"); + loader.unload(); +}; + +// Go over quota and handle it by listener. +exports.testQuotaExceededHandle = function (test) { + test.waitUntilDone(); + prefs.set(QUOTA_PREF, 18); + + let loader = Loader(module); + let ss = loader.require("sdk/simple-storage"); + ss.on("OverQuota", function () { + test.pass("OverQuota was emitted as expected"); + test.assertEqual(this, ss, "`this` should be simple storage"); + ss.storage = { x: 4, y: 5 }; + + manager(loader).jsonStore.onWrite = function () { + loader = Loader(module); + ss = loader.require("sdk/simple-storage"); + let numProps = 0; + for (let prop in ss.storage) + numProps++; + test.assert(numProps, 2, + "Store should contain 2 values: " + ss.storage.toSource()); + test.assertEqual(ss.storage.x, 4, "x value should be correct"); + test.assertEqual(ss.storage.y, 5, "y value should be correct"); + manager(loader).jsonStore.onWrite = function (storage) { + prefs.reset(QUOTA_PREF); + test.done(); + }; + loader.unload(); + }; + loader.unload(); + }); + // This will be JSON.stringify()ed to: {"a":1,"b":2,"c":3} (19 bytes) + ss.storage = { a: 1, b: 2, c: 3 }; + manager(loader).jsonStore.write(); +}; + +// Go over quota but don't handle it. The last good state should still persist. +exports.testQuotaExceededNoHandle = function (test) { + test.waitUntilDone(); + prefs.set(QUOTA_PREF, 5); + + let loader = Loader(module); + let ss = loader.require("sdk/simple-storage"); + + manager(loader).jsonStore.onWrite = function (storage) { + loader = Loader(module); + ss = loader.require("sdk/simple-storage"); + test.assertEqual(ss.storage, val, + "Value should have persisted: " + ss.storage); + ss.storage = "some very long string that is very long"; + ss.on("OverQuota", function () { + test.pass("OverQuota emitted as expected"); + manager(loader).jsonStore.onWrite = function () { + test.fail("Over-quota value should not have been written"); + }; + loader.unload(); + + loader = Loader(module); + ss = loader.require("sdk/simple-storage"); + test.assertEqual(ss.storage, val, + "Over-quota value should not have been written, " + + "old value should have persisted: " + ss.storage); + loader.unload(); + prefs.reset(QUOTA_PREF); + test.done(); + }); + manager(loader).jsonStore.write(); + }; + + let val = "foo"; + ss.storage = val; + loader.unload(); +}; + +exports.testQuotaUsage = function (test) { + test.waitUntilDone(); + + let quota = 21; + prefs.set(QUOTA_PREF, quota); + + let loader = Loader(module); + let ss = loader.require("sdk/simple-storage"); + + // {"a":1} (7 bytes) + ss.storage = { a: 1 }; + test.assertEqual(ss.quotaUsage, 7 / quota, "quotaUsage should be correct"); + + // {"a":1,"bb":2} (14 bytes) + ss.storage = { a: 1, bb: 2 }; + test.assertEqual(ss.quotaUsage, 14 / quota, "quotaUsage should be correct"); + + // {"a":1,"bb":2,"cc":3} (21 bytes) + ss.storage = { a: 1, bb: 2, cc: 3 }; + test.assertEqual(ss.quotaUsage, 21 / quota, "quotaUsage should be correct"); + + manager(loader).jsonStore.onWrite = function () { + prefs.reset(QUOTA_PREF); + test.done(); + }; + loader.unload(); +}; + +exports.testUninstall = function (test) { + test.waitUntilDone(); + let loader = Loader(module); + let ss = loader.require("sdk/simple-storage"); + manager(loader).jsonStore.onWrite = function () { + test.assert(file.exists(storeFilename), "Store file should exist"); + + loader = Loader(module); + ss = loader.require("sdk/simple-storage"); + loader.unload("uninstall"); + test.assert(!file.exists(storeFilename), "Store file should be removed"); + test.done(); + }; + ss.storage.foo = "foo"; + loader.unload(); +}; + +exports.testSetNoSetRead = function (test) { + test.waitUntilDone(); + + // Load the module, set a value. + let loader = Loader(module); + let ss = loader.require("sdk/simple-storage"); + manager(loader).jsonStore.onWrite = function (storage) { + test.assert(file.exists(storeFilename), "Store file should exist"); + + // Load the module again but don't access ss.storage. + loader = Loader(module); + ss = loader.require("sdk/simple-storage"); + manager(loader).jsonStore.onWrite = function (storage) { + test.fail("Nothing should be written since `storage` was not accessed."); + }; + loader.unload(); + + // Load the module a third time and make sure the value stuck. + loader = Loader(module); + ss = loader.require("sdk/simple-storage"); + manager(loader).jsonStore.onWrite = function (storage) { + file.remove(storeFilename); + test.done(); + }; + test.assertEqual(ss.storage.foo, val, "Value should persist"); + loader.unload(); + }; + let val = "foo"; + ss.storage.foo = val; + test.assertEqual(ss.storage.foo, val, "Value read should be value set"); + loader.unload(); +}; + + +function setGetRoot(test, val, compare) { + test.waitUntilDone(); + + compare = compare || function (a, b) a === b; + + // Load the module once, set a value. + let loader = Loader(module); + let ss = loader.require("sdk/simple-storage"); + manager(loader).jsonStore.onWrite = function () { + test.assert(file.exists(storeFilename), "Store file should exist"); + + // Load the module again and make sure the value stuck. + loader = Loader(module); + ss = loader.require("sdk/simple-storage"); + manager(loader).jsonStore.onWrite = function () { + file.remove(storeFilename); + test.done(); + }; + test.assert(compare(ss.storage, val), "Value should persist"); + loader.unload(); + }; + ss.storage = val; + test.assert(compare(ss.storage, val), "Value read should be value set"); + loader.unload(); +} + +function setGetRootError(test, val, msg) { + let pred = "storage must be one of the following types: " + + "array, boolean, null, number, object, string"; + let loader = Loader(module); + let ss = loader.require("sdk/simple-storage"); + test.assertRaises(function () ss.storage = val, pred, msg); + loader.unload(); +} diff --git a/tools/addon-sdk-1.12/test/test-tab-browser.js b/tools/addon-sdk-1.12/test/test-tab-browser.js new file mode 100644 index 0000000..7475f13 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-tab-browser.js @@ -0,0 +1,513 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var timer = require("sdk/timers"); +var {Cc,Ci} = require("chrome"); + +function onBrowserLoad(callback, event) { + if (event.target && event.target.defaultView == this) { + this.removeEventListener("load", onBrowserLoad, true); + let browsers = this.document.getElementsByTagName("tabbrowser"); + try { + timer.setTimeout(function (window) { + callback(window, browsers[0]); + }, 10, this); + } catch (e) { console.exception(e); } + } +} +// Utility function to open a new browser window. +function openBrowserWindow(callback, url) { + let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Ci.nsIWindowWatcher); + let urlString = Cc["@mozilla.org/supports-string;1"]. + createInstance(Ci.nsISupportsString); + urlString.data = url; + let window = ww.openWindow(null, "chrome://browser/content/browser.xul", + "_blank", "chrome,all,dialog=no", urlString); + if (callback) + window.addEventListener("load", onBrowserLoad.bind(window, callback), true); + + return window; +} + +// Helper for calling code at window close +function closeBrowserWindow(window, callback) { + timer.setTimeout(function() { + window.addEventListener("unload", function onUnload() { + window.removeEventListener("unload", onUnload, false); + callback(); + }, false); + window.close(); + }, 0); +} + +// Helper for opening two windows at once +function openTwoWindows(callback) { + openBrowserWindow(function (window1) { + openBrowserWindow(function (window2) { + callback(window1, window2); + }); + }); +} + +// Helper for closing two windows at once +function closeTwoWindows(window1, window2, callback) { + closeBrowserWindow(window1, function() { + closeBrowserWindow(window2, callback); + }); +} + +exports.testAddTab = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(window, browser) { + const tabBrowser = require("sdk/deprecated/tab-browser"); + + let cache = []; + let windowUtils = require("sdk/deprecated/window-utils"); + new windowUtils.WindowTracker({ + onTrack: function(win) { + cache.push(win); + }, + onUntrack: function(win) { + cache.splice(cache.indexOf(win), 1) + } + }); + let startWindowCount = cache.length; + + // Test 1: add a tab + let firstUrl = "data:text/html;charset=utf-8,one"; + tabBrowser.addTab(firstUrl, { + onLoad: function(e) { + let win1 = cache[startWindowCount - 1]; + test.assertEqual(win1.content.location, firstUrl, "URL of new tab in first window matches"); + + // Test 2: add a tab in a new window + let secondUrl = "data:text/html;charset=utf-8,two"; + tabBrowser.addTab(secondUrl, { + inNewWindow: true, + onLoad: function(e) { + test.assertEqual(cache.length, startWindowCount + 1, "a new window was opened"); + let win2 = cache[startWindowCount]; + let gBrowser = win2.gBrowser; + gBrowser.addEventListener("DOMContentLoaded", function onLoad(e) { + gBrowser.removeEventListener("DOMContentLoaded", onLoad, false); + test.assertEqual(win2.content.location, secondUrl, "URL of new tab in the new window matches"); + + closeBrowserWindow(win2, function() { + closeBrowserWindow(win1, function() { + test.done(); + }); + }); + }, false); + } + }); + } + }); + }); +}; + +exports.testTrackerWithDelegate = function(test) { + test.waitUntilDone(); + const tabBrowser = require("sdk/deprecated/tab-browser"); + + var delegate = { + state: "initializing", + onTrack: function onTrack(browser) { + if (this.state == "initializing") { + this.state = "waiting for browser window to open"; + } + else if (this.state == "waiting for browser window to open") { + this.state = "waiting for browser window to close"; + timer.setTimeout(function() { + closeBrowserWindow(browser.ownerDocument.defaultView, function() { + test.assertEqual(delegate.state, "deinitializing"); + tb.unload(); + test.done(); + }); + }, 0); + } + else + test.fail("invalid state"); + }, + onUntrack: function onUntrack(browser) { + if (this.state == "waiting for browser window to close") { + test.pass("proper state in onUntrack"); + this.state = "deinitializing"; + } + else if (this.state != "deinitializing") + test.fail("invalid state"); + } + }; + var tb = new tabBrowser.Tracker(delegate); + + delegate.state = "waiting for browser window to open"; + + openBrowserWindow(); +}; + +exports.testWhenContentLoaded = function(test) { + test.waitUntilDone(); + const tabBrowser = require("sdk/deprecated/tab-browser"); + + var tracker = tabBrowser.whenContentLoaded( + function(window) { + var item = window.document.getElementById("foo"); + test.assertEqual(item.textContent, "bar", + "whenContentLoaded() works."); + tracker.unload(); + closeBrowserWindow(activeWindow(), function() { + test.done(); + }); + }); + + openBrowserWindow(function(browserWindow, browser) { + var html = '<div id="foo">bar</div>'; + browser.addTab("data:text/html;charset=utf-8," + html); + }); +}; + +exports.testTrackerWithoutDelegate = function(test) { + test.waitUntilDone(); + const tabBrowser = require("sdk/deprecated/tab-browser"); + + openBrowserWindow(function(browserWindow, browser) { + var tb = new tabBrowser.Tracker(); + + if (tb.length == 0) + test.fail("expect at least one tab browser to exist."); + + for (var i = 0; i < tb.length; i++) + test.assertEqual(tb.get(i).nodeName, "tabbrowser", + "get() method and length prop should work"); + for (var b in tb) + test.assertEqual(b.nodeName, "tabbrowser", + "iterator should work"); + + var matches = [b for (b in tb) + if (b == browser)]; + test.assertEqual(matches.length, 1, + "New browser should be in tracker."); + tb.unload(); + + closeBrowserWindow(browserWindow, function() { + test.done(); + }); + }); +}; + +exports.testTabTracker = function(test) { + test.waitUntilDone(); + const tabBrowser = require("sdk/deprecated/tab-browser"); + + openBrowserWindow(function(browserWindow, browser) { + var delegate = { + tracked: 0, + onTrack: function(tab) { + this.tracked++; + }, + onUntrack: function(tab) { + this.tracked--; + } + }; + + let tabTracker = tabBrowser.TabTracker(delegate); + + let tracked = delegate.tracked; + let url1 = "data:text/html;charset=utf-8,1"; + let url2 = "data:text/html;charset=utf-8,2"; + let url3 = "data:text/html;charset=utf-8,3"; + let tabCount = 0; + + function tabLoadListener(e) { + let loadedURL = e.target.defaultView.location; + if (loadedURL == url1) + tabCount++; + else if (loadedURL == url2) + tabCount++; + else if (loadedURL == url3) + tabCount++; + + if (tabCount == 3) { + test.assertEqual(delegate.tracked, tracked + 3, "delegate tracked tabs matched count"); + tabTracker.unload(); + closeBrowserWindow(browserWindow, function() { + timer.setTimeout(function() test.done(), 0); + }); + } + } + + tabBrowser.addTab(url1, { + onLoad: tabLoadListener + }); + tabBrowser.addTab(url2, { + onLoad: tabLoadListener + }); + tabBrowser.addTab(url3, { + onLoad: tabLoadListener + }); + }); +}; + +exports.testActiveTab = function(test) { + test.waitUntilDone(); + openBrowserWindow(function(browserWindow, browser) { + const tabBrowser = require("sdk/deprecated/tab-browser"); + const TabModule = require("sdk/deprecated/tab-browser").TabModule; + let tm = new TabModule(browserWindow); + test.assertEqual(tm.length, 1); + let url1 = "data:text/html;charset=utf-8,foo"; + let url2 = "data:text/html;charset=utf-8,bar"; + + function tabURL(tab) tab.ownerDocument.defaultView.content.location.toString() + + tabBrowser.addTab(url1, { + onLoad: function(e) { + // make sure we're running in the right window. + test.assertEqual(tabBrowser.activeTab.ownerDocument.defaultView, browserWindow, "active window matches"); + browserWindow.focus(); + + test.assertEqual(tabURL(tabBrowser.activeTab), url1, "url1 matches"); + let tabIndex = browser.getBrowserIndexForDocument(e.target); + let tabAtIndex = browser.tabContainer.getItemAtIndex(tabIndex); + test.assertEqual(tabAtIndex, tabBrowser.activeTab, "activeTab element matches"); + + tabBrowser.addTab(url2, { + inBackground: true, + onLoad: function() { + test.assertEqual(tabURL(tabBrowser.activeTab), url1, "url1 still matches"); + let tabAtIndex = browser.tabContainer.getItemAtIndex(tabIndex); + test.assertEqual(tabAtIndex, tabBrowser.activeTab, "activeTab element matches"); + closeBrowserWindow(browserWindow, function() { + test.done() + }); + } + }); + } + }); + }); +}; + +// TabModule tests +exports.testEventsAndLengthStayInModule = function(test) { + test.waitUntilDone(); + let TabModule = require("sdk/deprecated/tab-browser").TabModule; + + openTwoWindows(function(window1, window2) { + let tm1 = new TabModule(window1); + let tm2 = new TabModule(window2); + + let counter1 = 0, counter2 = 0; + let counterTabs = 0; + + function onOpenListener() { + ++counterTabs; + if (counterTabs < 5) + return; + test.assertEqual(counter1, 2, "Correct number of events fired from window 1"); + test.assertEqual(counter2, 3, "Correct number of events fired from window 2"); + test.assertEqual(counterTabs, 5, "Correct number of events fired from all windows"); + test.assertEqual(tm1.length, 3, "Correct number of tabs in window 1"); + test.assertEqual(tm2.length, 4, "Correct number of tabs in window 2"); + closeTwoWindows(window1, window2, function() test.done()); + } + + tm1.onOpen = function() ++counter1 && onOpenListener(); + tm2.onOpen = function() ++counter2 && onOpenListener(); + + let url = "data:text/html;charset=utf-8,default"; + tm1.open(url); + tm1.open(url); + + tm2.open(url); + tm2.open(url); + tm2.open(url); + }); +} + +exports.testTabModuleActiveTab_getterAndSetter = function(test) { + test.waitUntilDone(); + let TabModule = require("sdk/deprecated/tab-browser").TabModule; + + openTwoWindows(function(window1, window2) { + let tm1 = new TabModule(window1); + let tm2 = new TabModule(window2); + + // First open two tabs per window + tm1.open({ + url: "data:text/html;charset=utf-8,<title>window1,tab1</title>", + onOpen: function(tab1) { + tm1.open({ + url: "data:text/html;charset=utf-8,<title>window1,tab2</title>", + onOpen: function (tab2) { + tm2.open({ + url: "data:text/html;charset=utf-8,<title>window2,tab1</title>", + onOpen: function (tab3) { + tm2.open({ + url: "data:text/html;charset=utf-8,<title>window2,tab2</title>", + onOpen: function(tab4) { + onTabsOpened(tab1, tab2, tab3, tab4); + } + }); + } + }); + } + }); + } + }); + + // Then try to activate tabs, but wait for all of them to be activated after + // being opened + function onTabsOpened(tab1, tab2, tab3, tab4) { + test.assertEqual(tm1.activeTab.title, "window1,tab2", + "Correct active tab on window 1"); + test.assertEqual(tm2.activeTab.title, "window2,tab2", + "Correct active tab on window 2"); + + tm1.onActivate = function onActivate() { + tm1.onActivate.remove(onActivate); + timer.setTimeout(function() { + test.assertEqual(tm1.activeTab.title, "window1,tab1", + "activeTab setter works (window 1)"); + test.assertEqual(tm2.activeTab.title, "window2,tab2", + "activeTab is ignored with tabs from another window"); + closeTwoWindows(window1, window2, function() test.done()); + }, 1000); + } + + tm1.activeTab = tab1; + // Setting activeTab from another window should have no effect: + tm1.activeTab = tab4; + } + + }); +} + +// test tabs iterator +exports.testTabModuleTabsIterator = function(test) { + test.waitUntilDone(); + let TabModule = require("sdk/deprecated/tab-browser").TabModule; + + openBrowserWindow(function(window) { + let tm1 = new TabModule(window); + let url = "data:text/html;charset=utf-8,default"; + tm1.open(url); + tm1.open(url); + tm1.open({ + url: url, + onOpen: function(tab) { + let count = 0; + for each (let t in tm1) count++; + test.assertEqual(count, 4, "iterated tab count matches"); + test.assertEqual(count, tm1.length, "length tab count matches"); + closeBrowserWindow(window, function() test.done()); + } + }); + }); +}; + +// inNewWindow parameter is ignored on single-window modules +exports.testTabModuleCantOpenInNewWindow = function(test) { + test.waitUntilDone(); + let TabModule = require("sdk/deprecated/tab-browser").TabModule; + + openBrowserWindow(function(window) { + let tm = new TabModule(window); + let url = "data:text/html;charset=utf-8,default"; + tm.open({ + url: url, + inNewWindow: true, + onOpen: function() { + test.assertEqual(tm.length, 2, "Tab was open on same window"); + closeBrowserWindow(window, function() test.done()); + } + }); + }); +}; + +// Test that having two modules attached to the same +// window won't duplicate events fired on each module +exports.testModuleListenersDontInteract = function(test) { + test.waitUntilDone(); + let TabModule = require("sdk/deprecated/tab-browser").TabModule; + + openBrowserWindow(function(window) { + let tm1 = new TabModule(window); + let tm2 = new TabModule(window); + + let url = "data:text/html;charset=utf-8,foo"; + let eventCount = 0, eventModule1 = 0, eventModule2 = 0; + + + let listener1 = function() { + // this should be called twice: when tab is open and when + // the url location is changed + eventCount++; + eventModule1++; + } + tm1.onReady = listener1; + + tm2.open({ + url: "about:blank", + onOpen: function(tab) { + // add listener via property assignment + let listener2 = function() { + eventCount++; + eventModule2++; + }; + tab.onReady = listener2; + + // add listener via collection add + let listener3 = function() { + eventCount++; + eventModule2++; + }; + tab.onReady.add(listener3); + + tab.location = url; + + test.waitUntilEqual(function () eventCount, 4, + "Correct global number of events") + .then(function () { + test.assertEqual(eventModule1, 2, + "Correct number of events on module 1"); + test.assertEqual(eventModule2, 2, + "Correct number of events on module 2"); + + tm1.onReady.remove(listener1); + tab.onReady.remove(listener2); + tab.onReady.remove(listener3); + closeBrowserWindow(window, function() test.done()); + }); + } + }); + }); +}; + +/******************* helpers *********************/ + +// Helper for getting the active window +function activeWindow() { + return Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator). + getMostRecentWindow("navigator:browser"); +}; + +// If the module doesn't support the app we're being run in, require() will +// throw. In that case, remove all tests above from exports, and add one dummy +// test that passes. +try { + require("sdk/deprecated/tab-browser"); +} +catch (err) { + // This bug should be mentioned in the error message. + let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=560716"; + if (err.message.indexOf(bug) < 0) + throw err; + + module.exports = { + testAppNotSupported: function (test) { + test.pass("the tab-browser module does not support this application."); + } + }; +} diff --git a/tools/addon-sdk-1.12/test/test-tab-observer.js b/tools/addon-sdk-1.12/test/test-tab-observer.js new file mode 100644 index 0000000..8b45c19 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-tab-observer.js @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { openTab, closeTab } = require("sdk/tabs/utils"); +const { Loader } = require("sdk/test/loader"); +const { setTimeout } = require("sdk/timers"); + +exports["test unload tab observer"] = function(assert, done) { + let loader = Loader(module); + + let window = loader.require("sdk/deprecated/window-utils").activeBrowserWindow; + let observer = loader.require("sdk/tabs/observer").observer; + let opened = 0; + let closed = 0; + + observer.on("open", function onOpen(window) { opened++; }); + observer.on("close", function onClose(window) { closed++; }); + + // Open and close tab to trigger observers. + closeTab(openTab(window, "data:text/html;charset=utf-8,tab-1")); + + // Unload the module so that all listeners set by observer are removed. + loader.unload(); + + // Open and close tab once again. + closeTab(openTab(window, "data:text/html;charset=utf-8,tab-2")); + + // Enqueuing asserts to make sure that assertion is not performed early. + setTimeout(function () { + assert.equal(1, opened, "observer open was called before unload only"); + assert.equal(1, closed, "observer close was called before unload only"); + done(); + }, 0); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-tab.js b/tools/addon-sdk-1.12/test/test-tab.js new file mode 100644 index 0000000..280266c --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-tab.js @@ -0,0 +1,111 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const tabAPI = require('sdk/tabs/tab'); +const tabs = require("sdk/tabs"); // From addon-kit +const windowUtils = require("sdk/deprecated/window-utils"); +const { getTabForWindow } = require('sdk/tabs/helpers'); + +// The primary test tab +var primaryTab; + +// We have an auxiliary tab to test background tabs. +var auxTab; + +// The window for the outer iframe in the primary test page +var iframeWin; + +exports.testGetTabForWindow = function(test) { + test.waitUntilDone(); + + test.assertEqual(getTabForWindow(windowUtils.activeWindow), null, + "getTabForWindow return null on topwindow"); + test.assertEqual(getTabForWindow(windowUtils.activeBrowserWindow), null, + "getTabForWindow return null on topwindow"); + + let subSubDocument = encodeURIComponent( + 'Sub iframe<br/>'+ + '<iframe id="sub-sub-iframe" src="data:text/html;charset=utf-8,SubSubIframe" />'); + let subDocument = encodeURIComponent( + 'Iframe<br/>'+ + '<iframe id="sub-iframe" src="data:text/html;charset=utf-8,'+subSubDocument+'" />'); + let url = 'data:text/html;charset=utf-8,' + encodeURIComponent( + 'Content<br/><iframe id="iframe" src="data:text/html;charset=utf-8,'+subDocument+'" />'); + + // Open up a new tab in the background. + // + // This lets us test whether GetTabForWindow works even when the tab in + // question is not active. + tabs.open({ + inBackground: true, + url: "about:mozilla", + onReady: function(tab) { auxTab = tab; step2(url, test);}, + onActivate: function(tab) { step3(test); } + }); +}; + +function step2(url, test) { + + tabs.open({ + url: url, + onReady: function(tab) { + primaryTab = tab; + let window = windowUtils.activeBrowserWindow.content; + + let matchedTab = getTabForWindow(window); + test.assertEqual(matchedTab, tab, + "We are able to find the tab with his content window object"); + + let timer = require("sdk/timers"); + function waitForFrames() { + let iframe = window.document.getElementById("iframe"); + if (!iframe) { + timer.setTimeout(waitForFrames, 100); + return; + } + iframeWin = iframe.contentWindow; + let subIframe = iframeWin.document.getElementById("sub-iframe"); + if (!subIframe) { + timer.setTimeout(waitForFrames, 100); + return; + } + let subIframeWin = subIframe.contentWindow; + let subSubIframe = subIframeWin.document.getElementById("sub-sub-iframe"); + if (!subSubIframe) { + timer.setTimeout(waitForFrames, 100); + return; + } + let subSubIframeWin = subSubIframe.contentWindow; + + matchedTab = getTabForWindow(iframeWin); + test.assertEqual(matchedTab, tab, + "We are able to find the tab with an iframe window object"); + + matchedTab = getTabForWindow(subIframeWin); + test.assertEqual(matchedTab, tab, + "We are able to find the tab with a sub-iframe window object"); + + matchedTab = getTabForWindow(subSubIframeWin); + test.assertEqual(matchedTab, tab, + "We are able to find the tab with a sub-sub-iframe window object"); + + // Put our primary tab in the background and test again. + // The onActivate listener will take us to step3. + auxTab.activate(); + } + waitForFrames(); + } + }); +} + +function step3(test) { + + let matchedTab = getTabForWindow(iframeWin); + test.assertEqual(matchedTab, primaryTab, + "We get the correct tab even when it's in the background"); + + primaryTab.close(function () { + auxTab.close(function () { test.done();}); + }); +} diff --git a/tools/addon-sdk-1.12/test/test-tabs-common.js b/tools/addon-sdk-1.12/test/test-tabs-common.js new file mode 100644 index 0000000..3d65143 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-tabs-common.js @@ -0,0 +1,296 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { Loader } = require('sdk/test/loader'); +const { browserWindows } = require('sdk/windows'); +const tabs = require('sdk/tabs'); + +const URL = 'data:text/html;charset=utf-8,<html><head><title>#title#</title></head></html>'; + +// TEST: tab count +exports.testTabCounts = function(test) { + test.waitUntilDone(); + + tabs.open({ + url: 'about:blank', + onReady: function(tab) { + let count1 = 0, + count2 = 0; + for each(let window in browserWindows) { + count1 += window.tabs.length; + for each(let tab in window.tabs) { + count2 += 1; + } + } + + test.assert(tabs.length > 1, 'tab count is > 1'); + test.assertEqual(count1, tabs.length, 'tab count by length is correct'); + test.assertEqual(count2, tabs.length, 'tab count by iteration is correct'); + + // end test + tab.close(function() test.done()); + } + }); +}; + +// TEST: tab.activate() +exports.testActiveTab_setter_alt = function(test) { + test.waitUntilDone(); + + let url = URL.replace("#title#", "testActiveTab_setter_alt"); + let tab1URL = URL.replace("#title#", "tab1"); + + tabs.open({ + url: tab1URL, + onReady: function(activeTab) { + let activeTabURL = tabs.activeTab.url; + + tabs.open({ + url: url, + inBackground: true, + onReady: function onReady(tab) { + test.assertEqual(tabs.activeTab.url, activeTabURL, "activeTab url has not changed"); + test.assertEqual(tab.url, url, "url of new background tab matches"); + + tab.once('activate', function onActivate(eventTab) { + test.assertEqual(tabs.activeTab.url, url, "url after activeTab setter matches"); + test.assertEqual(eventTab, tab, "event argument is the activated tab"); + test.assertEqual(eventTab, tabs.activeTab, "the tab is the active one"); + + activeTab.close(function() { + tab.close(function() { + // end test + test.done(); + }); + }); + }); + + tab.activate(); + } + }); + } + }); +}; + +// TEST: tab.close() +exports.testTabClose_alt = function(test) { + test.waitUntilDone(); + + let url = URL.replace('#title#', 'TabClose_alt'); + let tab1URL = URL.replace('#title#', 'tab1'); + + tabs.open({ + url: tab1URL, + onReady: function(tab1) { + // make sure that our tab is not active first + test.assertNotEqual(tabs.activeTab.url, url, "tab is not the active tab"); + + tabs.open({ + url: url, + onReady: function(tab) { + test.assertEqual(tab.url, url, "tab is now the active tab"); + test.assertEqual(tabs.activeTab.url, url, "tab is now the active tab"); + + // another tab should be activated on close + tabs.once('activate', function() { + test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab"); + + // end test + tab1.close(function() test.done()); + }); + + tab.close(); + } + }); + } + }); +}; + +exports.testAttachOnOpen_alt = function (test) { + // Take care that attach has to be called on tab ready and not on tab open. + test.waitUntilDone(); + + tabs.open({ + url: "data:text/html;charset=utf-8,foobar", + onOpen: function (tab) { + let worker = tab.attach({ + contentScript: 'self.postMessage(document.location.href); ', + onMessage: function (msg) { + test.assertEqual(msg, "about:blank", + "Worker document url is about:blank on open"); + worker.destroy(); + tab.close(function() test.done()); + } + }); + } + }); +}; + +exports.testAttachOnMultipleDocuments_alt = function (test) { + // Example of attach that process multiple tab documents + test.waitUntilDone(); + + let firstLocation = "data:text/html;charset=utf-8,foobar"; + let secondLocation = "data:text/html;charset=utf-8,bar"; + let thirdLocation = "data:text/html;charset=utf-8,fox"; + let onReadyCount = 0; + let worker1 = null; + let worker2 = null; + let detachEventCount = 0; + + tabs.open({ + url: firstLocation, + onReady: function (tab) { + onReadyCount++; + if (onReadyCount == 1) { + worker1 = tab.attach({ + contentScript: 'self.on("message", ' + + ' function () self.postMessage(document.location.href)' + + ');', + onMessage: function (msg) { + test.assertEqual(msg, firstLocation, + "Worker url is equal to the 1st document"); + tab.url = secondLocation; + }, + onDetach: function () { + detachEventCount++; + test.pass("Got worker1 detach event"); + test.assertRaises(function () { + worker1.postMessage("ex-1"); + }, + /Couldn't find the worker/, + "postMessage throw because worker1 is destroyed"); + checkEnd(); + } + }); + worker1.postMessage("new-doc-1"); + } + else if (onReadyCount == 2) { + worker2 = tab.attach({ + contentScript: 'self.on("message", ' + + ' function () self.postMessage(document.location.href)' + + ');', + onMessage: function (msg) { + test.assertEqual(msg, secondLocation, + "Worker url is equal to the 2nd document"); + tab.url = thirdLocation; + }, + onDetach: function () { + detachEventCount++; + test.pass("Got worker2 detach event"); + test.assertRaises(function () { + worker2.postMessage("ex-2"); + }, + /Couldn't find the worker/, + "postMessage throw because worker2 is destroyed"); + checkEnd(tab); + } + }); + worker2.postMessage("new-doc-2"); + } + else if (onReadyCount == 3) { + tab.close(); + } + } + }); + + function checkEnd(tab) { + if (detachEventCount != 2) + return; + + test.pass("Got all detach events"); + + // end test + test.done(); + } +}; + +exports.testAttachWrappers_alt = function (test) { + // Check that content script has access to wrapped values by default + test.waitUntilDone(); + + let document = "data:text/html;charset=utf-8,<script>var globalJSVar = true; " + + " document.getElementById = 3;</script>"; + let count = 0; + + tabs.open({ + url: document, + onReady: function (tab) { + let worker = tab.attach({ + contentScript: 'try {' + + ' self.postMessage(!("globalJSVar" in window));' + + ' self.postMessage(typeof window.globalJSVar == "undefined");' + + '} catch(e) {' + + ' self.postMessage(e.message);' + + '}', + onMessage: function (msg) { + test.assertEqual(msg, true, "Worker has wrapped objects ("+count+")"); + if (count++ == 1) + tab.close(function() test.done()); + } + }); + } + }); +}; + +// TEST: activeWindow getter and activeTab getter on tab 'activate' event +exports.testActiveWindowActiveTabOnActivate_alt = function(test) { + test.waitUntilDone(); + + let activateCount = 0; + let newTabs = []; + let tabs = browserWindows.activeWindow.tabs; + + tabs.on('activate', function onActivate(tab) { + test.assertEqual(tabs.activeTab, tab, + "the active window's active tab is the tab provided"); + + if (++activateCount == 2) { + tabs.removeListener('activate', onActivate); + + newTabs.forEach(function(tab) { + tab.close(function() { + if (--activateCount == 0) { + // end test + test.done(); + } + }); + }); + } + else if (activateCount > 2) { + test.fail("activateCount is greater than 2 for some reason.."); + } + }); + + tabs.open({ + url: URL.replace("#title#", "tabs.open1"), + onOpen: function(tab) newTabs.push(tab) + }); + tabs.open({ + url: URL.replace("#title#", "tabs.open2"), + onOpen: function(tab) newTabs.push(tab) + }); +}; + +// TEST: tab properties +exports.testTabContentTypeAndReload = function(test) { + test.waitUntilDone(); + + let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>"; + let urlXML = "data:text/xml;charset=utf-8,<foo>bar</foo>"; + tabs.open({ + url: url, + onReady: function(tab) { + if (tab.url === url) { + test.assertEqual(tab.contentType, "text/html"); + tab.url = urlXML; + } + else { + test.assertEqual(tab.contentType, "text/xml"); + tab.close(function() test.done()); + } + } + }); +}; diff --git a/tools/addon-sdk-1.12/test/test-tabs.js b/tools/addon-sdk-1.12/test/test-tabs.js new file mode 100644 index 0000000..cad8931 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-tabs.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +if (require("sdk/system/xul-app").is("Firefox")) { + module.exports = require("./tabs/test-firefox-tabs"); +} +else if (require("sdk/system/xul-app").is("Fennec")) { + module.exports = require("./tabs/test-fennec-tabs"); +} diff --git a/tools/addon-sdk-1.12/test/test-text-streams.js b/tools/addon-sdk-1.12/test/test-text-streams.js new file mode 100644 index 0000000..36a01c8 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-text-streams.js @@ -0,0 +1,156 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim:set ts=2 sw=2 sts=2 et filetype=javascript + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const file = require("sdk/io/file"); +const { pathFor } = require("sdk/system"); +const { Loader } = require("sdk/test/loader"); + +const STREAM_CLOSED_ERROR = "The stream is closed and cannot be used."; + +// This should match the constant of the same name in text-streams.js. +const BUFFER_BYTE_LEN = 0x8000; + +exports.testWriteRead = function (test) { + let fname = dataFileFilename(); + + // Write a small string less than the stream's buffer size... + let str = "exports.testWriteRead data!"; + let stream = file.open(fname, "w"); + test.assert(!stream.closed, "stream.closed after open should be false"); + stream.write(str); + stream.close(); + test.assert(stream.closed, "stream.closed after close should be true"); + test.assertRaises(function () stream.close(), + STREAM_CLOSED_ERROR, + "stream.close after already closed should raise error"); + test.assertRaises(function () stream.write("This shouldn't be written!"), + STREAM_CLOSED_ERROR, + "stream.write after close should raise error"); + + // ... and read it. + stream = file.open(fname); + test.assert(!stream.closed, "stream.closed after open should be false"); + test.assertEqual(stream.read(), str, + "stream.read should return string written"); + test.assertEqual(stream.read(), "", + "stream.read at EOS should return empty string"); + stream.close(); + test.assert(stream.closed, "stream.closed after close should be true"); + test.assertRaises(function () stream.close(), + STREAM_CLOSED_ERROR, + "stream.close after already closed should raise error"); + test.assertRaises(function () stream.read(), + STREAM_CLOSED_ERROR, + "stream.read after close should raise error"); + + // Write a big string many times the size of the stream's buffer and read it. + // Since it comes after the previous test, this also ensures that the file is + // truncated when it's opened for writing. + str = ""; + let bufLen = BUFFER_BYTE_LEN; + let fileSize = bufLen * 10; + for (let i = 0; i < fileSize; i++) + str += i % 10; + stream = file.open(fname, "w"); + stream.write(str); + stream.close(); + stream = file.open(fname); + test.assertEqual(stream.read(), str, + "stream.read should return string written"); + stream.close(); + + // The same, but write and read in chunks. + stream = file.open(fname, "w"); + let i = 0; + while (i < str.length) { + // Use a chunk length that spans buffers. + let chunk = str.substr(i, bufLen + 1); + stream.write(chunk); + i += bufLen + 1; + } + stream.close(); + stream = file.open(fname); + let readStr = ""; + bufLen = BUFFER_BYTE_LEN; + let readLen = bufLen + 1; + do { + var frag = stream.read(readLen); + readStr += frag; + } while (frag); + stream.close(); + test.assertEqual(readStr, str, + "stream.write and read in chunks should work as expected"); + + // Read the same file, passing in strange numbers of bytes to read. + stream = file.open(fname); + test.assertEqual(stream.read(fileSize * 100), str, + "stream.read with big byte length should return string " + + "written"); + stream.close(); + + stream = file.open(fname); + test.assertEqual(stream.read(0), "", + "string.read with zero byte length should return empty " + + "string"); + stream.close(); + + stream = file.open(fname); + test.assertEqual(stream.read(-1), "", + "string.read with negative byte length should return " + + "empty string"); + stream.close(); + + file.remove(fname); +}; + +exports.testWriteAsync = function (test) { + test.waitUntilDone(); + + let fname = dataFileFilename(); + let str = "exports.testWriteAsync data!"; + let stream = file.open(fname, "w"); + test.assert(!stream.closed, "stream.closed after open should be false"); + + // Write. + stream.writeAsync(str, function (err) { + test.assertEqual(this, stream, "|this| should be the stream object"); + test.assertEqual(err, undefined, + "stream.writeAsync should not cause error"); + test.assert(stream.closed, "stream.closed after write should be true"); + test.assertRaises(function () stream.close(), + STREAM_CLOSED_ERROR, + "stream.close after already closed should raise error"); + test.assertRaises(function () stream.writeAsync("This shouldn't work!"), + STREAM_CLOSED_ERROR, + "stream.writeAsync after close should raise error"); + + // Read. + stream = file.open(fname, "r"); + test.assert(!stream.closed, "stream.closed after open should be false"); + let readStr = stream.read(); + test.assertEqual(readStr, str, + "string.read should yield string written"); + stream.close(); + file.remove(fname); + test.done(); + }); +}; + +exports.testUnload = function (test) { + let loader = Loader(module); + let file = loader.require("sdk/io/file"); + + let filename = dataFileFilename("temp"); + let stream = file.open(filename, "w"); + + loader.unload(); + test.assert(stream.closed, "stream should be closed after module unload"); +}; + +// Returns the name of a file that should be used to test writing and reading. +function dataFileFilename() { + return file.join(pathFor("ProfD"), "test-text-streams-data"); +} diff --git a/tools/addon-sdk-1.12/test/test-timer.js b/tools/addon-sdk-1.12/test/test-timer.js new file mode 100644 index 0000000..2c7d846 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-timer.js @@ -0,0 +1,131 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var timer = require("sdk/timers"); +const { Loader } = require("sdk/test/loader"); + +exports.testSetTimeout = function(test) { + timer.setTimeout(function() { + test.pass("testSetTimeout passed"); + test.done(); + }, 1); + test.waitUntilDone(); +}; + +exports.testParamedSetTimeout = function(test) { + let params = [1, 'foo', { bar: 'test' }, null, undefined]; + timer.setTimeout.apply(null, [function() { + test.assertEqual(arguments.length, params.length); + for (let i = 0, ii = params.length; i < ii; i++) + test.assertEqual(params[i], arguments[i]); + test.done(); + }, 1].concat(params)); + test.waitUntilDone(); +}; + +exports.testClearTimeout = function(test) { + var myFunc = function myFunc() { + test.fail("myFunc() should not be called in testClearTimeout"); + }; + var id = timer.setTimeout(myFunc, 1); + timer.setTimeout(function() { + test.pass("testClearTimeout passed"); + test.done(); + }, 2); + timer.clearTimeout(id); + test.waitUntilDone(); +}; + +exports.testParamedClearTimeout = function(test) { + let params = [1, 'foo', { bar: 'test' }, null, undefined]; + var myFunc = function myFunc() { + test.fail("myFunc() should not be called in testClearTimeout"); + }; + var id = timer.setTimeout(myFunc, 1); + timer.setTimeout.apply(null, [function() { + test.assertEqual(arguments.length, params.length); + for (let i = 0, ii = params.length; i < ii; i++) + test.assertEqual(params[i], arguments[i]); + test.done(); + }, 1].concat(params)); + timer.clearTimeout(id); + test.waitUntilDone(); +}; + +exports.testSetInterval = function (test) { + var count = 0; + var id = timer.setInterval(function () { + count++; + if (count >= 5) { + timer.clearInterval(id); + test.pass("testSetInterval passed"); + test.done(); + } + }, 1); + test.waitUntilDone(); +}; + +exports.testParamedSetInerval = function(test) { + let params = [1, 'foo', { bar: 'test' }, null, undefined]; + let count = 0; + let id = timer.setInterval.apply(null, [function() { + count ++; + if (count < 5) { + test.assertEqual(arguments.length, params.length); + for (let i = 0, ii = params.length; i < ii; i++) + test.assertEqual(params[i], arguments[i]); + } else { + timer.clearInterval(id); + test.done(); + } + }, 1].concat(params)); + test.waitUntilDone(); +}; + +exports.testClearInterval = function (test) { + timer.clearInterval(timer.setInterval(function () { + test.fail("setInterval callback should not be called"); + }, 1)); + var id = timer.setInterval(function () { + timer.clearInterval(id); + test.pass("testClearInterval passed"); + test.done(); + }, 2); + test.waitUntilDone(); +}; + +exports.testParamedClearInterval = function(test) { + timer.clearInterval(timer.setInterval(function () { + test.fail("setInterval callback should not be called"); + }, 1, timer, {}, null)); + + let id = timer.setInterval(function() { + timer.clearInterval(id); + test.assertEqual(3, arguments.length); + test.done(); + }, 2, undefined, 'test', {}); + test.waitUntilDone(); +}; + + +exports.testUnload = function(test) { + var loader = Loader(module); + var sbtimer = loader.require("sdk/timers"); + + var myFunc = function myFunc() { + test.fail("myFunc() should not be called in testUnload"); + }; + + sbtimer.setTimeout(myFunc, 1); + sbtimer.setTimeout(myFunc, 1, 'foo', 4, {}, undefined); + sbtimer.setInterval(myFunc, 1); + sbtimer.setInterval(myFunc, 1, {}, null, 'bar', undefined, 87); + loader.unload(); + timer.setTimeout(function() { + test.pass("timer testUnload passed"); + test.done(); + }, 2); + test.waitUntilDone(); +}; + diff --git a/tools/addon-sdk-1.12/test/test-tmp-file.js b/tools/addon-sdk-1.12/test/test-tmp-file.js new file mode 100644 index 0000000..187f9e7 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-tmp-file.js @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const tmp = require("sdk/test/tmp-file"); +const file = require("sdk/io/file"); + +const testFolderURL = module.uri.split('test-tmp-file.js')[0]; + +exports.testCreateFromString = function (test) { + let expectedContent = "foo"; + let path = tmp.createFromString(expectedContent); + let content = file.read(path); + test.assertEqual(content, expectedContent, + "Temporary file contains the expected content"); +} + +exports.testCreateFromURL = function (test) { + let url = testFolderURL + "test-tmp-file.txt"; + let path = tmp.createFromURL(url); + let content = file.read(path); + test.assertEqual(content, "foo", + "Temporary file contains the expected content"); +} diff --git a/tools/addon-sdk-1.12/test/test-tmp-file.txt b/tools/addon-sdk-1.12/test/test-tmp-file.txt new file mode 100644 index 0000000..1910281 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-tmp-file.txt @@ -0,0 +1 @@ +foo
\ No newline at end of file diff --git a/tools/addon-sdk-1.12/test/test-traceback.js b/tools/addon-sdk-1.12/test/test-traceback.js new file mode 100644 index 0000000..df310d1 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-traceback.js @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var traceback = require("sdk/console/traceback"); +var {Cc,Ci,Cr,Cu} = require("chrome"); + +function throwNsIException() { + var ios = Cc['@mozilla.org/network/io-service;1'] + .getService(Ci.nsIIOService); + ios.newURI("i'm a malformed URI", null, null); +} + +function throwError() { + throw new Error("foob"); +} + +exports.testFormatDoesNotFetchRemoteFiles = function(test) { + var observers = require("sdk/deprecated/observer-service"); + ["http", "https"].forEach( + function(scheme) { + var httpRequests = 0; + function onHttp() { + httpRequests++; + } + + observers.add("http-on-modify-request", onHttp); + + try { + var tb = [{filename: scheme + "://www.mozilla.org/", + lineNo: 1, + funcName: "blah"}]; + traceback.format(tb); + } catch (e) { + test.exception(e); + } + + observers.remove("http-on-modify-request", onHttp); + + test.assertEqual(httpRequests, 0, + "traceback.format() does not make " + + scheme + " request"); + }); +}; + +exports.testFromExceptionWithString = function(test) { + try { + throw "foob"; + test.fail("an exception should've been thrown"); + } catch (e if e == "foob") { + var tb = traceback.fromException(e); + test.assertEqual(tb.length, 0); + } +}; + +exports.testFormatWithString = function(test) { + // This can happen if e.g. a thrown exception was + // a string instead of an Error instance. + test.assertEqual(traceback.format("blah"), + "Traceback (most recent call last):"); +}; + +exports.testFromExceptionWithError = function(test) { + try { + throwError(); + test.fail("an exception should've been thrown"); + } catch (e if e instanceof Error) { + var tb = traceback.fromException(e); + var xulApp = require("sdk/system/xul-app"); + test.assertEqual(tb.slice(-1)[0].funcName, "throwError"); + } +}; + +exports.testFromExceptionWithNsIException = function(test) { + try { + throwNsIException(); + test.fail("an exception should've been thrown"); + } catch (e if e.result == Cr.NS_ERROR_MALFORMED_URI) { + var tb = traceback.fromException(e); + test.assertEqual(tb.slice(-1)[0].funcName, + "throwNsIException"); + } +}; + +exports.testFormat = function(test) { + function getTraceback() { + return traceback.format(); + } + + var formatted = getTraceback(); + test.assertEqual(typeof(formatted), "string"); + var lines = formatted.split("\n"); + test.assertEqual(lines.slice(-2)[0].indexOf("getTraceback") > 0, + true, + "formatted traceback should include function name"); + test.assertEqual(lines.slice(-1)[0].trim(), + "return traceback.format();", + "formatted traceback should include source code"); +}; + +exports.testExceptionsWithEmptyStacksAreLogged = function(test) { + // Ensures that our fix to bug 550368 works. + var sandbox = Cu.Sandbox("http://www.foo.com"); + var excRaised = false; + try { + Cu.evalInSandbox("returns 1 + 2;", sandbox, "1.8", + "blah.js", 25); + } catch (e) { + excRaised = true; + var stack = traceback.fromException(e); + test.assertEqual(stack.length, 1, "stack should have one frame"); + test.assert(stack[0].filename, "blah.js", "frame should have filename"); + test.assert(stack[0].lineNo, 25, "frame should have line no"); + test.assertEqual(stack[0].funcName, null, "frame should have null function name"); + } + if (!excRaised) + test.fail("Exception should have been raised."); +}; diff --git a/tools/addon-sdk-1.12/test/test-traits-core.js b/tools/addon-sdk-1.12/test/test-traits-core.js new file mode 100644 index 0000000..8ab2edd --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-traits-core.js @@ -0,0 +1,838 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const ERR_CONFLICT = 'Remaining conflicting property: ', + ERR_REQUIRED = 'Missing required property: '; + +function assertSametrait(test, trait1, trait2) { + let names1 = Object.getOwnPropertyNames(trait1), + names2 = Object.getOwnPropertyNames(trait2); + + test.assertEqual( + names1.length, + names2.length, + 'equal traits must have same amount of properties' + ); + + for (let i = 0; i < names1.length; i++) { + let name = names1[i]; + test.assertNotEqual( + -1, + names2.indexOf(name), + 'equal traits must contain same named properties: ' + name + ); + assertSameDescriptor(test, name, trait1[name], trait2[name]); + } +} + +function assertSameDescriptor(test, name, desc1, desc2) { + if (desc1.conflict || desc2.conflict) { + test.assertEqual( + desc1.conflict, + desc2.conflict, + 'if one of same descriptors has `conflict` another must have it: ' + + name + ); + } else if (desc1.required || desc2.required) { + test.assertEqual( + desc1.required, + desc2.required, + 'if one of same descriptors is has `required` another must have it: ' + + name + ); + } else { + test.assertEqual( + desc1.get, + desc2.get, + 'get must be the same on both descriptors: ' + name + ); + test.assertEqual( + desc1.set, + desc2.set, + 'set must be the same on both descriptors: ' + name + ); + test.assertEqual( + desc1.value, + desc2.value, + 'value must be the same on both descriptors: ' + name + ); + test.assertEqual( + desc1.enumerable, + desc2.enumerable, + 'enumerable must be the same on both descriptors: ' + name + ); + test.assertEqual( + desc1.required, + desc2.required, + 'value must be the same on both descriptors: ' + name + ); + } +} + +function Data(value, enumerable, confligurable, writable) { + return { + value: value, + enumerable: false !== enumerable, + confligurable: false !== confligurable, + writable: false !== writable + }; +} + +function Method(method, enumerable, confligurable, writable) { + return { + value: method, + enumerable: false !== enumerable, + confligurable: false !== confligurable, + writable: false !== writable + }; +} + +function Accessor(get, set, enumerable, confligurable) { + return { + get: get, + set: set, + enumerable: false !== enumerable, + confligurable: false !== confligurable, + }; +} + +function Required(name) { + function required() { throw new Error(ERR_REQUIRED + name) } + return { + get: required, + set: required, + required: true + }; +} + +function Conflict(name) { + function conflict() { throw new Error(ERR_CONFLICT + name) } + return { + get: conflict, + set: conflict, + conflict: true + }; +} + +function testMethod() {}; + +const { trait, compose, resolve, required, override, create } = + require('sdk/deprecated/traits/core'); + + +exports['test:empty trait'] = function(test) { + assertSametrait( + test, + trait({}), + {} + ); +}; + +exports['test:simple trait'] = function(test) { + assertSametrait( + test, + trait({ + a: 0, + b: testMethod + }), + { + a: Data(0, true, true, true), + b: Method(testMethod, true, true, true) + } + ); +}; + +exports['test:simple trait with required prop'] = function(test) { + assertSametrait( + test, + trait({ + a: required, + b: 1 + }), + { + a: Required('a'), + b: Data(1) + } + ); +}; + +exports['test:ordering of trait properties is irrelevant'] = function(test) { + assertSametrait(test, + trait({ a: 0, b: 1, c: required }), + trait({ b: 1, c: required, a: 0 }) + ); +}; + +exports['test:trait with accessor property'] = function(test) { + let record = { get a() {}, set a(v) {} }; + let get = Object.getOwnPropertyDescriptor(record,'a').get; + let set = Object.getOwnPropertyDescriptor(record,'a').set; + assertSametrait(test, + trait(record), + { a: Accessor(get, set ) } + ); +}; + +exports['test:simple composition'] = function(test) { + assertSametrait(test, + compose( + trait({ a: 0, b: 1 }), + trait({ c: 2, d: testMethod }) + ), + { + a: Data(0), + b: Data(1), + c: Data(2), + d: Method(testMethod) + } + ); +}; + +exports['test:composition with conflict'] = function(test) { + assertSametrait(test, + compose( + trait({ a: 0, b: 1 }), + trait({ a: 2, c: testMethod }) + ), + { + a: Conflict('a'), + b: Data(1), + c: Method(testMethod) + } + ); +}; + +exports['test:composition of identical props does not cause conflict'] = +function(test) { + assertSametrait(test, + compose( + trait({ a: 0, b: 1 }), + trait({ a: 0, c: testMethod }) + ), + { + a: Data(0), + b: Data(1), + c: Method(testMethod) } + ) +}; + +exports['test:composition with identical required props'] = +function(test) { + assertSametrait(test, + compose( + trait({ a: required, b: 1 }), + trait({ a: required, c: testMethod }) + ), + { + a: Required(), + b: Data(1), + c: Method(testMethod) + } + ); +}; + +exports['test:composition satisfying a required prop'] = function (test) { + assertSametrait(test, + compose( + trait({ a: required, b: 1 }), + trait({ a: testMethod }) + ), + { + a: Method(testMethod), + b: Data(1) + } + ); +}; + +exports['test:compose is neutral wrt conflicts'] = function (test) { + assertSametrait(test, + compose( + compose( + trait({ a: 1 }), + trait({ a: 2 }) + ), + trait({ b: 0 }) + ), + { + a: Conflict('a'), + b: Data(0) + } + ); +}; + +exports['test:conflicting prop overrides required prop'] = function (test) { + assertSametrait(test, + compose( + compose( + trait({ a: 1 }), + trait({ a: 2 }) + ), + trait({ a: required }) + ), + { + a: Conflict('a') + } + ); +}; + +exports['test:compose is commutative'] = function (test) { + assertSametrait(test, + compose( + trait({ a: 0, b: 1 }), + trait({ c: 2, d: testMethod }) + ), + compose( + trait({ c: 2, d: testMethod }), + trait({ a: 0, b: 1 }) + ) + ); +}; + +exports['test:compose is commutative, also for required/conflicting props'] = +function (test) { + assertSametrait(test, + compose( + trait({ a: 0, b: 1, c: 3, e: required }), + trait({ c: 2, d: testMethod }) + ), + compose( + trait({ c: 2, d: testMethod }), + trait({ a: 0, b: 1, c: 3, e: required }) + ) + ); +}; +exports['test:compose is associative'] = function (test) { + assertSametrait(test, + compose( + trait({ a: 0, b: 1, c: 3, d: required }), + compose( + trait({ c: 3, d: required }), + trait({ c: 2, d: testMethod, e: 'foo' }) + ) + ), + compose( + compose( + trait({ a: 0, b: 1, c: 3, d: required }), + trait({ c: 3, d: required }) + ), + trait({ c: 2, d: testMethod, e: 'foo' }) + ) + ); +}; + +exports['test:diamond import of same prop does not generate conflict'] = +function (test) { + assertSametrait(test, + compose( + compose( + trait({ b: 2 }), + trait({ a: 1 }) + ), + compose( + trait({ c: 3 }), + trait({ a: 1 }) + ), + trait({ d: 4 }) + ), + { + a: Data(1), + b: Data(2), + c: Data(3), + d: Data(4) + } + ); +}; + +exports['test:resolve with empty resolutions has no effect'] = +function (test) { + assertSametrait(test, resolve({}, trait({ + a: 1, + b: required, + c: testMethod + })), { + a: Data(1), + b: Required(), + c: Method(testMethod) + }); +}; + +exports['test:resolve: renaming'] = function (test) { + assertSametrait(test, + resolve( + { a: 'A', c: 'C' }, + trait({ a: 1, b: required, c: testMethod }) + ), + { + A: Data(1), + b: Required(), + C: Method(testMethod), + a: Required(), + c: Required() + } + ); +}; + +exports['test:resolve: renaming to conflicting name causes conflict, order 1'] += function (test) { + assertSametrait(test, + resolve( + { a: 'b'}, + trait({ a: 1, b: 2 }) + ), + { + b: Conflict('b'), + a: Required() + } + ); +}; + +exports['test:resolve: renaming to conflicting name causes conflict, order 2'] += function (test) { + assertSametrait(test, + resolve( + { a: 'b' }, + trait({ b: 2, a: 1 }) + ), + { + b: Conflict('b'), + a: Required() + } + ); +}; + +exports['test:resolve: simple exclusion'] = function (test) { + assertSametrait(test, + resolve( + { a: undefined }, + trait({ a: 1, b: 2 }) + ), + { + a: Required(), + b: Data(2) + } + ); +}; + +exports['test:resolve: exclusion to "empty" trait'] = function (test) { + assertSametrait(test, + resolve( + { a: undefined, b: undefined }, + trait({ a: 1, b: 2 }) + ), + { + a: Required(), + b: Required() + } + ); +}; + +exports['test:resolve: exclusion and renaming of disjoint props'] = +function (test) { + assertSametrait(test, + resolve( + { a: undefined, b: 'c' }, + trait({ a: 1, b: 2 }) + ), + { + a: Required(), + c: Data(2), + b: Required() + } + ); +}; + +exports['test:resolve: exclusion and renaming of overlapping props'] = +function (test) { + assertSametrait(test, + resolve( + { a: undefined, b: 'a' }, + trait({ a: 1, b: 2 }) + ), + { + a: Data(2), + b: Required() + } + ); +}; + +exports['test:resolve: renaming to a common alias causes conflict'] = +function (test) { + assertSametrait(test, + resolve( + { a: 'c', b: 'c' }, + trait({ a: 1, b: 2 }) + ), + { + c: Conflict('c'), + a: Required(), + b: Required() + } + ); +}; + +exports['test:resolve: renaming overrides required target'] = +function (test) { + assertSametrait(test, + resolve( + { b: 'a' }, + trait({ a: required, b: 2 }) + ), + { + a: Data(2), + b: Required() + } + ); +}; + +exports['test:resolve: renaming required properties has no effect'] = +function (test) { + assertSametrait(test, + resolve( + { b: 'a' }, + trait({ a: 2, b: required }) + ), + { + a: Data(2), + b: Required() + } + ); +}; + +exports['test:resolve: renaming of non-existent props has no effect'] = +function (test) { + assertSametrait(test, + resolve( + { a: 'c', d: 'c' }, + trait({ a: 1, b: 2 }) + ), + { + c: Data(1), + b: Data(2), + a: Required() + } + ); +}; + +exports['test:resolve: exclusion of non-existent props has no effect'] = +function (test) { + assertSametrait(test, + resolve( + { b: undefined }, + trait({ a: 1 }) + ), + { + a: Data(1) + } + ); +}; + +exports['test:resolve is neutral w.r.t. required properties'] = +function (test) { + assertSametrait(test, + resolve( + { a: 'c', b: undefined }, + trait({ a: required, b: required, c: 'foo', d: 1 }) + ), + { + a: Required(), + b: Required(), + c: Data('foo'), + d: Data(1) + } + ); +}; + +exports['test:resolve supports swapping of property names, ordering 1'] = +function (test) { + assertSametrait(test, + resolve( + { a: 'b', b: 'a' }, + trait({ a: 1, b: 2 }) + ), + { + a: Data(2), + b: Data(1) + } + ); +}; + +exports['test:resolve supports swapping of property names, ordering 2'] = +function (test) { + assertSametrait(test, + resolve( + { b: 'a', a: 'b' }, + trait({ a: 1, b: 2 }) + ), + { + a: Data(2), + b: Data(1) + } + ); +}; + +exports['test:resolve supports swapping of property names, ordering 3'] = +function (test) { + assertSametrait(test, + resolve( + { b: 'a', a: 'b' }, + trait({ b: 2, a: 1 }) + ), + { + a: Data(2), + b: Data(1) + } + ); +}; + +exports['test:resolve supports swapping of property names, ordering 4'] = +function (test) { + assertSametrait(test, + resolve( + { a: 'b', b: 'a' }, + trait({ b: 2, a: 1 }) + ), + { + a: Data(2), + b: Data(1) + } + ); +}; + +exports['test:override of mutually exclusive traits'] = function (test) { + assertSametrait(test, + override( + trait({ a: 1, b: 2 }), + trait({ c: 3, d: testMethod }) + ), + { + a: Data(1), + b: Data(2), + c: Data(3), + d: Method(testMethod) + } + ); +}; + +exports['test:override of mutually exclusive traits is compose'] = +function (test) { + assertSametrait(test, + override( + trait({ a: 1, b: 2 }), + trait({ c: 3, d: testMethod }) + ), + compose( + trait({ d: testMethod, c: 3 }), + trait({ b: 2, a: 1 }) + ) + ); +}; + +exports['test:override of overlapping traits'] = function (test) { + assertSametrait(test, + override( + trait({ a: 1, b: 2 }), + trait({ a: 3, c: testMethod }) + ), + { + a: Data(1), + b: Data(2), + c: Method(testMethod) + } + ); +}; + +exports['test:three-way override of overlapping traits'] = function (test) { + assertSametrait(test, + override( + trait({ a: 1, b: 2 }), + trait({ b: 4, c: 3 }), + trait({ a: 3, c: testMethod, d: 5 }) + ), + { + a: Data(1), + b: Data(2), + c: Data(3), + d: Data(5) + } + ); +}; + +exports['test:override replaces required properties'] = function (test) { + assertSametrait(test, + override( + trait({ a: required, b: 2 }), + trait({ a: 1, c: testMethod }) + ), + { + a: Data(1), + b: Data(2), + c: Method(testMethod) + } + ); +}; + +exports['test:override is not commutative'] = function (test) { + assertSametrait(test, + override( + trait({ a: 1, b: 2 }), + trait({ a: 3, c: 4 }) + ), + { + a: Data(1), + b: Data(2), + c: Data(4) + } + ); + + assertSametrait(test, + override( + trait({ a: 3, c: 4 }), + trait({ a: 1, b: 2 }) + ), + { + a: Data(3), + b: Data(2), + c: Data(4) + } + ); +}; + +exports['test:override is associative'] = function (test) { + assertSametrait(test, + override( + override( + trait({ a: 1, b: 2 }), + trait({ a: 3, c: 4, d: 5 }) + ), + trait({ a: 6, c: 7, e: 8 }) + ), + override( + trait({ a: 1, b: 2 }), + override( + trait({ a: 3, c: 4, d: 5 }), + trait({ a: 6, c: 7, e: 8 }) + ) + ) + ); +}; + +exports['test:create simple'] = function(test) { + let o1 = create( + Object.prototype, + trait({ a: 1, b: function() { return this.a; } }) + ); + + test.assertEqual( + Object.prototype, + Object.getPrototypeOf(o1), + 'o1 prototype' + ); + test.assertEqual(1, o1.a, 'o1.a'); + test.assertEqual(1, o1.b(), 'o1.b()'); + test.assertEqual( + 2, + Object.getOwnPropertyNames(o1).length, + 'Object.keys(o1).length === 2' + ); +}; + +exports['test:create with Array.prototype'] = function(test) { + let o2 = create(Array.prototype, trait({})); + test.assertEqual( + Array.prototype, + Object.getPrototypeOf(o2), + "o2 prototype" + ); +}; + +exports['test:exception for incomplete required properties'] = +function(test) { + try { + create(Object.prototype, trait({ foo: required })); + test.fail('expected create to complain about missing required props'); + } catch(e) { + test.assertEqual( + 'Error: Missing required property: foo', + e.toString(), + 'required prop error' + ); + } +}; + +exports['test:exception for unresolved conflicts'] = function(test) { + try { + create({}, compose(trait({ a: 0 }), trait({ a: 1 }))); + test.fail('expected create to complain about unresolved conflicts'); + } catch(e) { + test.assertEqual( + 'Error: Remaining conflicting property: a', + e.toString(), + 'conflicting prop error' + ); + } +}; + +exports['test:verify that required properties are present but undefined'] = +function(test) { + try { + let o4 = Object.create(Object.prototype, trait({ foo: required })); + test.assertEqual(true, 'foo' in o4, 'required property present'); + try { + let foo = o4.foo; + test.fail('access to required property must throw'); + } catch(e) { + test.assertEqual( + 'Error: Missing required property: foo', + e.toString(), + 'required prop error' + ) + } + } catch(e) { + test.fail('did not expect create to complain about required props'); + } +}; + +exports['test:verify that conflicting properties are present'] = +function(test) { + try { + let o5 = Object.create( + Object.prototype, + compose(trait({ a: 0 }), trait({ a: 1 })) + ); + test.assertEqual(true, 'a' in o5, 'conflicting property present'); + try { + let a = o5.a; // accessors or data prop + test.fail('expected conflicting prop to cause exception'); + } catch (e) { + test.assertEqual( + 'Error: Remaining conflicting property: a', + e.toString(), + 'conflicting prop access error' + ); + } + } catch(e) { + test.fail('did not expect create to complain about conflicting props'); + } +}; + +exports['test diamond with conflicts'] = function(test) { + function makeT1(x) trait({ m: function() { return x; } }) + function makeT2(x) compose(trait({ t2: 'foo' }), makeT1(x)) + function makeT3(x) compose(trait({ t3: 'bar' }), makeT1(x)) + + let T4 = compose(makeT2(5), makeT3(5)); + try { + let o = create(Object.prototype, T4); + test.fail('expected diamond prop to cause exception'); + } catch(e) { + test.assertEqual( + 'Error: Remaining conflicting property: m', + e.toString(), + 'diamond prop conflict' + ); + } +}; + diff --git a/tools/addon-sdk-1.12/test/test-traits.js b/tools/addon-sdk-1.12/test/test-traits.js new file mode 100644 index 0000000..8e01a70 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-traits.js @@ -0,0 +1,398 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Trait } = require('sdk/deprecated/traits'); + +exports['test:simple compose'] = function(test) { + let List = Trait.compose({ + _list: null, + constructor: function List() { + this._list = []; + }, + list: function list() this._list.slice(0), + add: function add(item) this._list.push(item), + remove: function remove(item) { + let list = this._list; + let index = list.indexOf(item); + if (0 <= index) list.slice(index, 1); + } + }); + + test.assertNotEqual(undefined, List, 'should not be undefined'); + test.assertEqual('function', typeof List, 'type should be function'); + test.assertEqual( + Trait.compose, + List.compose, + 'should inherit static compose' + ); + test.assertEqual( + Trait.override, + List.override, + 'should inherit static override' + ); + test.assertEqual( + Trait.required, + List.required, + 'should inherit static required' + ); + test.assertEqual( + Trait.resolve, + List.resolve, + 'should inherit static resolve' + ); + + test.assert( + !('_list' in List.prototype), + 'should not expose private API' + ); +} +exports['test: compose trait instance and create instance'] = function(test) { + let List = Trait.compose({ + constructor: function List(options) { + this._list = []; + this._public.publicMember = options.publicMember; + }, + _privateMember: true, + get privateMember() this._privateMember, + get list() this._list.slice(0), + add: function add(item) this._list.push(item), + remove: function remove(item) { + let list = this._list + let index = list.indexOf(item) + if (0 <= index) list.slice(index, 1) + } + }); + let list = List({ publicMember: true }); + + test.assertEqual('object', typeof list, 'should return an object') + test.assertEqual( + true, + list instanceof List, + 'should be instance of a List' + ); + + test.assertEqual( + undefined, + list._privateMember, + 'instance should not expose private API' + ); + + test.assertEqual( + true, + list.privateMember, + 'privates are accessible by public API' + ); + + list._privateMember = false; + + test.assertEqual( + true, + list.privateMember, + 'property changes on instance must not affect privates' + ); + + test.assert( + !('_list' in list), + 'instance should not expose private members' + ); + + test.assertEqual( + true, + list.publicMember, + 'public members are exposed' + ) + test.assertEqual( + 'function', + typeof list.add, + 'should be function' + ) + test.assertEqual( + 'function', + typeof list.remove, + 'should be function' + ); + + list.add(1); + test.assertEqual( + 1, + list.list[0], + 'exposed public API should be able of modifying privates' + ) +}; + + +exports['test:instances must not be hackable'] = function(test) { + let SECRET = 'There is no secret!', + secret = null; + + let Class = Trait.compose({ + _secret: null, + protect: function(data) this._secret = data + }); + + let i1 = Class(); + i1.protect(SECRET); + + test.assertEqual( + undefined, + (function() this._secret).call(i1), + 'call / apply can\'t access private state' + ); + + let proto = Object.getPrototypeOf(i1); + try { + proto.reveal = function() this._secret; + secret = i1.reveal(); + } catch(e) {} + test.assertNotEqual( + SECRET, + secret, + 'public __proto__ changes should not affect privates' + ); + secret = null; + + let Class2 = Trait.compose({ + _secret: null, + protect: function(data) this._secret = data + }); + let i2 = Class2(); + i2.protect(SECRET); + try { + Object.prototype.reveal = function() this._secret; + secret = i2.reveal(); + } catch(e) {} + test.assertNotEqual( + SECRET, + secret, + 'Object.prototype changes must not affect instances' + ); +} + +exports['test:instanceof'] = function(test) { + const List = Trait.compose({ + // private API: + _list: null, + // public API + constructor: function List() { + this._list = [] + }, + get length() this._list.length, + add: function add(item) this._list.push(item), + remove: function remove(item) { + let list = this._list; + let index = list.indexOf(item); + if (0 <= index) list.slice(index, 1); + } + }); + + test.assert(List() instanceof List, 'Must be instance of List'); + test.assert(new List() instanceof List, 'Must be instance of List'); +}; + +exports['test:privates are unaccessible'] = function(test) { + const List = Trait.compose({ + // private API: + _list: null, + // public API + constructor: function List() { + this._list = []; + }, + get length() this._list.length, + add: function add(item) this._list.push(item), + remove: function remove(item) { + let list = this._list; + let index = list.indexOf(item); + if (0 <= index) list.slice(index, 1); + } + }); + + let list = List(); + test.assert(!('_list' in list), 'no privates on instance'); + test.assert( + !('_list' in List.prototype), + 'no privates on prototype' + ); +}; + +exports['test:public API can access private API'] = function(test) { + const List = Trait.compose({ + // private API: + _list: null, + // public API + constructor: function List() { + this._list = []; + }, + get length() this._list.length, + add: function add(item) this._list.push(item), + remove: function remove(item) { + let list = this._list; + let index = list.indexOf(item); + if (0 <= index) list.slice(index, 1); + } + }); + let list = List(); + + list.add('test'); + + test.assertEqual( + 1, + list.length, + 'should be able to add element and access it from public getter' + ); +}; + +exports['test:required'] = function(test) { + const Enumerable = Trait.compose({ + list: Trait.required, + forEach: function forEach(consumer) { + return this.list.forEach(consumer); + } + }); + + try { + let i = Enumerable(); + test.fail('should throw when creating instance with required properties'); + } catch(e) { + test.assertEqual( + 'Error: Missing required property: list', + e.toString(), + 'required prop error' + ); + } +}; + +exports['test:compose with required'] = function(test) { + const List = Trait.compose({ + // private API: + _list: null, + // public API + constructor: function List() { + this._list = []; + }, + get length() this._list.length, + add: function add(item) this._list.push(item), + remove: function remove(item) { + let list = this._list; + let index = list.indexOf(item); + if (0 <= index) list.slice(index, 1); + } + }); + + const Enumerable = Trait.compose({ + list: Trait.required, + forEach: function forEach(consumer) { + return this.list.forEach(consumer); + } + }); + + const EnumerableList = Enumerable.compose({ + get list() this._list.slice(0) + }, List); + + let array = [1,2, 'ab'] + let l = EnumerableList(array); + array.forEach(function(element) l.add(element)); + let number = 0; + l.forEach(function(element, index) { + number ++; + test.assertEqual(array[index], element, 'should mach array element') + }); + test.assertEqual( + array.length, + number, + 'should perform as many asserts as elements in array' + ); +}; + +exports['test:resolve'] = function(test) { + const List = Trait.compose({ + // private API: + _list: null, + // public API + constructor: function List() { + this._list = []; + }, + get length() this._list.length, + add: function add(item) this._list.push(item), + remove: function remove(item) { + let list = this._list; + let index = list.indexOf(item); + if (0 <= index) list.slice(index, 1); + } + }); + + const Range = List.resolve({ + constructor: null, + add: '_add', + }).compose({ + min: null, + max: null, + get list() this._list.slice(0), + constructor: function Range(min, max) { + this.min = min; + this.max = max; + this._list = []; + }, + add: function(item) { + if (item <= this.max && item >= this.min) + this._add(item) + } + }); + + let r = Range(0, 10); + + test.assertEqual( + 0, + r.min, + 'constructor must have set min' + ); + test.assertEqual( + 10, + r.max, + 'constructor must have set max' + ); + + test.assertEqual( + 0, + r.length, + 'should not contain any elements' + ); + + r.add(5); + + test.assertEqual( + 1, + r.length, + 'should add `5` to list' + ); + + r.add(12); + + test.assertEqual( + 1, + r.length, + 'should not add `12` to list' + ); +}; + +exports['test:custom iterator'] = function(test) { + let Sub = Trait.compose({ + foo: "foo", + bar: "bar", + baz: "baz", + __iterator__: function() { + yield 1; + yield 2; + yield 3; + } + }); + + let (i = 0, sub = Sub()) { + for (let item in sub) + test.assertEqual(++i, item, "iterated item has the right value"); + }; +}; + diff --git a/tools/addon-sdk-1.12/test/test-type.js b/tools/addon-sdk-1.12/test/test-type.js new file mode 100644 index 0000000..99e11de --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-type.js @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict" + +var utils = require("sdk/lang/type"); + +exports["test function"] = function (assert) { + assert.ok(utils.isFunction(function(){}), "value is function"); + assert.ok(utils.isFunction(Object), "Object is function"); + assert.ok(utils.isFunction(new Function("")), "Genertaed value is function"); + assert.ok(!utils.isFunction({}), "object is not a function"); + assert.ok(!utils.isFunction(4), "number is not a function"); +}; + +exports["test atoms"] = function (assert) { + assert.ok(utils.isPrimitive(2), "number is primitive"); + assert.ok(utils.isPrimitive(NaN), "`NaN` is primitve"); + assert.ok(utils.isPrimitive(undefined), "`undefined` is primitive"); + assert.ok(utils.isPrimitive(null), "`null` is primitive"); + assert.ok(utils.isPrimitive(Infinity), "`Infinity` is primitive"); + assert.ok(utils.isPrimitive("foo"), "strings are primitive"); + assert.ok(utils.isPrimitive(true) && utils.isPrimitive(false), + "booleans are primitive"); +}; + +exports["test object"] = function (assert) { + assert.ok(utils.isObject({}), "`{}` is object"); + assert.ok(!utils.isObject(null), "`null` is not an object"); + assert.ok(!utils.isObject(Object), "functions is not an object"); +}; + +exports["test flat objects"] = function (assert) { + assert.ok(utils.isFlat({}), "`{}` is a flat object"); + assert.ok(!utils.isFlat([]), "`[]` is not a flat object"); + assert.ok(!utils.isFlat(new function() {}), "derived objects are not flat"); + assert.ok(utils.isFlat(Object.prototype), "Object.prototype is flat"); +}; + +exports["test json atoms"] = function (assert) { + assert.ok(utils.isJSON(null), "`null` is JSON"); + assert.ok(utils.isJSON(undefined), "`undefined` is JSON"); + assert.ok(utils.isJSON(NaN), "`NaN` is JSON"); + assert.ok(utils.isJSON(Infinity), "`Infinity` is JSON"); + assert.ok(utils.isJSON(true) && utils.isJSON(false), "booleans are JSON"); + assert.ok(utils.isJSON(4), utils.isJSON(0), "numbers are JSON"); + assert.ok(utils.isJSON("foo bar"), "strings are JSON"); +}; + +exports["test instanceOf"] = function (assert) { + assert.ok(utils.instanceOf(assert, Object), + "assert is object from other sandbox"); + assert.ok(utils.instanceOf(new Date(), Date), "instance of date"); + assert.ok(!utils.instanceOf(null, Object), "null is not an instance"); +}; + +exports["test json"] = function (assert) { + assert.ok(!utils.isJSON(function(){}), "functions are not json"); + assert.ok(utils.isJSON({}), "`{}` is JSON"); + assert.ok(utils.isJSON({ + a: "foo", + b: 3, + c: undefined, + d: null, + e: { + f: { + g: "bar", + p: [{}, "oueou", 56] + }, + q: { nan: NaN, infinity: Infinity }, + "non standard name": "still works" + } + }), "JSON can contain nested objects"); + + var foo = {}; + var bar = { foo: foo }; + foo.bar = bar; + assert.ok(!utils.isJSON(foo), "recursive objects are not json"); + + + assert.ok(!utils.isJSON({ get foo() { return 5 } }), + "json can not have getter"); + + assert.ok(!utils.isJSON({ foo: "bar", baz: function () {} }), + "json can not contain functions"); + + assert.ok(!utils.isJSON(Object.create({})), + "json must be direct descendant of `Object.prototype`"); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-unit-test.js b/tools/addon-sdk-1.12/test/test-unit-test.js new file mode 100644 index 0000000..d6a1451 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-unit-test.js @@ -0,0 +1,254 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const timer = require("sdk/timers"); +const { Loader } = require("sdk/test/loader"); + +var setupCalled = false, teardownCalled = false; + +exports.setup = function() { + setupCalled = true; +}; + +exports.teardown = function() { + teardownCalled = true; + setupCalled = false; +}; + +// Important note - unit tests are run in alphabetical order. The following +// unit tests for setup/teardown are order dependent, sometimes the result of +// one test is checked in the next test (testing for teardown does this). When +// tests are cohesively a single unit, they are named <test_name> - partN where +// N is their order in the sequence. Secondly, because these tests should be +// run before all others, they start with an A. +exports.testASetupTeardownSyncTestPart1 = function(test) { + test.assertEqual(true, setupCalled, 'setup function was called before this'); + test.assertEqual(false, teardownCalled, 'teardown function was not called before this'); +}; + +exports.testASetupTeardownSyncTestPart2 = function(test) { + test.assertEqual(true, setupCalled, 'setup was re-called before this'); + test.assertEqual(true, teardownCalled, 'teardown was called after first function'); +}; + +exports.testATeardownAsyncTestPart1 = function(test) { + teardownCalled = false; + + timer.setTimeout(function() { + test.assertEqual(false, teardownCalled, "teardown not called until done"); + test.done(); + }, 200); + test.waitUntilDone(); +}; + +exports.testATeardownAsyncTestPart2 = function(test) { + test.assertEqual(true, teardownCalled, "teardown called after done"); +}; + +exports.testWaitUntilInstant = function(test) { + test.waitUntilDone(); + + test.waitUntil(function () true, "waitUntil with instant true pass") + .then(function () test.done()); +} + +exports.testWaitUntil = function(test) { + test.waitUntilDone(); + let succeed = false; + + test.waitUntil(function () succeed, "waitUntil pass") + .then(function () test.done()); + + timer.setTimeout(function () { + succeed = true; + }, 200); +} + +exports.testWaitUntilEqual = function(test) { + test.waitUntilDone(); + let succeed = false; + + test.waitUntilEqual("foo", function () succeed ? "foo" : "bar", + "waitUntilEqual pass") + .then(function () test.done()); + + timer.setTimeout(function () { + succeed = true; + }, 200); +} + +exports.testWaitUntilNotEqual = function(test) { + test.waitUntilDone(); + let succeed = false; + + test.waitUntilNotEqual("foo", function () succeed ? "bar" : "foo", + "waitUntilNotEqual pass") + .then(function () test.done()); + + timer.setTimeout(function () { + succeed = true; + }, 200); +} + +exports.testWaitUntilMatches = function(test) { + test.waitUntilDone(); + let succeed = false; + + test.waitUntilMatches(function () succeed ? "foo" : "bar", + /foo/, "waitUntilEqual pass") + .then(function () test.done()); + + timer.setTimeout(function () { + succeed = true; + }, 200); +} + +exports.testWaitUntilErrorInCallback = function(test) { + test.waitUntilDone(); + + test.expectFail(function() { + test.waitUntil(function () {throw "oops"}, "waitUntil pass") + .then(function () test.done()); + }); +} + +exports.testWaitUntilTimeoutInCallback = function(test) { + test.waitUntilDone(1000); + + let runner = new (require("sdk/deprecated/unit-test").TestRunner)({ + console: { + calls: 0, + error: function(msg) { + this.calls++; + if (this.calls == 1) + test.assertEqual(arguments[0], "TEST FAILED: wait4ever (timed out)"); + else if (this.calls == 2) { + test.assertEqual(arguments[0], "test assertion never became true:\n"); + test.assertEqual(arguments[1], "assertion failed, value is false\n"); + // We could additionally check that arguments[1] contains the correct + // stack, but it would be difficult to do so given that it contains + // resource: URLs with a randomly generated string embedded in them + // (the ID of the test addon created to run the tests). And in any + // case, checking the arguments seems sufficient. + + test.done(); + } + else { + test.fail("We got unexpected console.error() calls from waitUntil" + + " assertion callback: '" + arguments[1] + "'"); + } + }, + info: function (msg) { + test.assertEqual(msg, "executing 'wait4ever'"); + }, + trace: function () {} + } + }); + + runner.start({ + test: { + name: "wait4ever", + testFunction: function(test) { + test.waitUntilDone(100); + test.waitUntil(function() false); + } + }, + onDone: function() {} + }); +}; + +exports.testExpectFail = function(test) { + test.expectFail(function() { + test.fail('expectFail masking .fail'); + }); + + test.expectFail(function() { + test.assert(false, 'expectFail masking .assert'); + }); + + test.assert(true, 'assert should pass with no expectFail'); +/* + test.expectFail(function() { + test.expectFail(function() { + test.fail('this should blow up'); + }); + }); +*/ +}; + +exports.testAssertFunction = function(test) { + test.assertFunction(function() {}, 'assertFunction with function'); + test.expectFail(function() { + test.assertFunction(null, 'assertFunction with non-function'); + }); +}; + +exports.testAssertUndefined = function(test) { + test.assertUndefined(undefined, 'assertUndefined with undefined'); + test.expectFail(function() { + test.assertUndefined(null, 'assertUndefined with null'); + }); + test.expectFail(function() { + test.assertUndefined(false, 'assertUndefined with false'); + }); + test.expectFail(function() { + test.assertUndefined(0, 'assertUndefined with 0'); + }); +}; + +exports.testAssertNotUndefined = function(test) { + test.expectFail(function() { + test.assertNotUndefined(undefined, 'assertNotUndefined with undefined'); + }); + test.assertNotUndefined(null, 'assertNotUndefined with null'); + test.assertNotUndefined(false, 'assertNotUndefined with false'); + test.assertNotUndefined(0, 'assertNotUndefined with 0'); +}; + +exports.testAssertNull = function(test) { + test.assertNull(null, 'assertNull with null'); + test.expectFail(function() { + test.assertNull(undefined, 'assertNull with undefined'); + }); + test.expectFail(function() { + test.assertNull(false, 'assertNull with false'); + }); + test.expectFail(function() { + test.assertNull(0, 'assertNull with 0'); + }); +}; + +exports.testAssertNotNull = function(test) { + test.assertNotNull(undefined, 'assertNotNull with undefined'); + test.assertNotNull(false, 'assertNotNull with false'); + test.assertNotNull(0, 'assertNotNull with 0'); + + test.expectFail(function() { + test.assertNotNull(null, 'testAssertNotNull with null'); + }); +}; + +exports.testAssertObject = function(test) { + test.assertObject({}, 'assertObject with {}' ); + test.assertObject(new Object(), 'assertObject with new Object'); + test.expectFail(function() { + test.assertObject('fail', 'assertObject with string'); + }); +}; + +exports.testAssertString = function(test) { + test.assertString('', 'assertString with ""'); + test.assertString(new String(), 'assertString with new String'); +}; + +exports.testAssertArray = function(test) { + test.assertArray([], 'assertArray with []'); + test.assertArray(new Array(), 'assertArray with new Array'); +}; + +exports.testNumber = function(test) { + test.assertNumber(1, 'assertNumber with 1'); + test.assertNumber(new Number('2'), 'assertNumber with new Number("2")' ); +}; + diff --git a/tools/addon-sdk-1.12/test/test-unload.js b/tools/addon-sdk-1.12/test/test-unload.js new file mode 100644 index 0000000..1bbb64e --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-unload.js @@ -0,0 +1,167 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var unload = require("sdk/system/unload"); +var { Loader } = require("sdk/test/loader"); + +exports.testUnloading = function(test) { + var loader = Loader(module, { + console: Object.create(console, { + exception: { value: function(error) { + exceptions.push(error); + }} + }) + }); + var exceptions = []; + var ul = loader.require("sdk/system/unload"); + var unloadCalled = 0; + function unload() { + unloadCalled++; + throw new Error("error"); + } + ul.when(unload); + + // This should be ignored, as we already registered it + ul.when(unload); + + function unload2() { unloadCalled++; } + ul.when(unload2); + loader.unload(); + test.assertEqual(unloadCalled, 2, + "Unloader functions are called on unload."); + test.assertEqual(exceptions.length, 1, + "One unload handler threw exception"); +}; + +exports.testEnsure = function(test) { + test.assertRaises(function() { unload.ensure({}); }, + "object has no 'unload' property", + "passing obj with no unload prop should fail"); + test.assertRaises(function() { unload.ensure({}, "destroy"); }, + "object has no 'destroy' property", + "passing obj with no custom unload prop should fail"); + + var called = 0; + var obj = {unload: function() { called++; }}; + + unload.ensure(obj); + obj.unload(); + test.assertEqual(called, 1, + "unload() should be called"); + obj.unload(); + test.assertEqual(called, 1, + "unload() should be called only once"); +}; + +/** + * Check that destructors are called only once with Traits. + * - check that public API is calling the destructor and unregister it, + * - check that composed traits with multiple ensure calls, leads to only + * one destructor call. + */ +exports.testEnsureWithTraits = function(test) { + + let { Trait } = require("sdk/deprecated/traits"); + let loader = Loader(module); + let ul = loader.require("sdk/system/unload"); + + let called = 0; + let composedCalled = 0; + let composedTrait = Trait.compose({ + constructor: function () { + // We have to give "public interface" of this trait, as we want to + // call public `unload` method and ensure that we call it only once, + // either when we call this public function manually or on add-on unload + ul.ensure(this._public); + }, + unload: function unload() { + composedCalled++; + } + }); + let obj = Trait.compose( + composedTrait.resolve({ + constructor: "_constructor", + unload : "_unload" + }), { + constructor: function constructor() { + // Same thing applies here, we need to pass public interface + ul.ensure(this._public); + this._constructor(); + }, + unload: function unload() { + called++; + this._unload(); + } + })(); + + obj.unload(); + test.assertEqual(called, 1, + "unload() should be called"); + + test.assertEqual(composedCalled, 1, + "composed object unload() should be called"); + + obj.unload(); + test.assertEqual(called, 1, + "unload() should be called only once"); + test.assertEqual(composedCalled, 1, + "composed object unload() should be called only once"); + + loader.unload(); + test.assertEqual(called, 1, + "unload() should be called only once, after addon unload"); + test.assertEqual(composedCalled, 1, + "composed object unload() should be called only once, " + + "after addon unload"); +}; + +exports.testEnsureWithTraitsPrivate = function(test) { + + let { Trait } = require("sdk/deprecated/traits"); + let loader = Loader(module); + let ul = loader.require("sdk/system/unload"); + + let called = 0; + let privateObj = null; + let obj = Trait.compose({ + constructor: function constructor() { + // This time wa don't have to give public interface, + // as we want to call a private method: + ul.ensure(this, "_unload"); + privateObj = this; + }, + _unload: function unload() { + called++; + this._unload(); + } + })(); + + loader.unload(); + test.assertEqual(called, 1, + "unload() should be called"); + + privateObj._unload(); + test.assertEqual(called, 1, + "_unload() should be called only once, after addon unload"); +}; + +exports.testReason = function (test) { + var reason = "Reason doesn't actually have to be anything in particular."; + var loader = Loader(module); + var ul = loader.require("sdk/system/unload"); + ul.when(function (rsn) { + test.assertEqual(rsn, reason, + "when() reason should be reason given to loader"); + }); + var obj = { + unload: function (rsn) { + test.assertEqual(rsn, reason, + "ensure() reason should be reason given to loader"); + } + }; + ul.ensure(obj); + loader.unload(reason); +}; diff --git a/tools/addon-sdk-1.12/test/test-url.js b/tools/addon-sdk-1.12/test/test-url.js new file mode 100644 index 0000000..3b5cec2 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-url.js @@ -0,0 +1,255 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var url = require("sdk/url"); +var { packed } = require("sdk/self"); + +exports.testResolve = function(test) { + test.assertEqual(url.URL("bar", "http://www.foo.com/").toString(), + "http://www.foo.com/bar"); + + test.assertEqual(url.URL("bar", "http://www.foo.com"), + "http://www.foo.com/bar"); + + test.assertEqual(url.URL("http://bar.com/", "http://foo.com/"), + "http://bar.com/", + "relative should override base"); + + test.assertRaises(function() { url.URL("blah"); }, + "malformed URI: blah", + "url.resolve() should throw malformed URI on base"); + + test.assertRaises(function() { url.URL("chrome://global"); }, + "invalid URI: chrome://global", + "url.resolve() should throw invalid URI on base"); + + test.assertRaises(function() { url.URL("chrome://foo/bar"); }, + "invalid URI: chrome://foo/bar", + "url.resolve() should throw on bad chrome URI"); + + test.assertEqual(url.URL("", "http://www.foo.com"), + "http://www.foo.com/", + "url.resolve() should add slash to end of domain"); +}; + +exports.testParseHttp = function(test) { + var info = url.URL("http://foo.com/bar"); + test.assertEqual(info.scheme, "http"); + test.assertEqual(info.host, "foo.com"); + test.assertEqual(info.port, null); + test.assertEqual(info.userPass, null); + test.assertEqual(info.path, "/bar"); +}; + +exports.testParseHttpWithPort = function(test) { + var info = url.URL("http://foo.com:5/bar"); + test.assertEqual(info.port, 5); +}; + +exports.testParseChrome = function(test) { + var info = url.URL("chrome://global/content/blah"); + test.assertEqual(info.scheme, "chrome"); + test.assertEqual(info.host, "global"); + test.assertEqual(info.port, null); + test.assertEqual(info.userPass, null); + test.assertEqual(info.path, "/content/blah"); +}; + +exports.testParseAbout = function(test) { + var info = url.URL("about:boop"); + test.assertEqual(info.scheme, "about"); + test.assertEqual(info.host, null); + test.assertEqual(info.port, null); + test.assertEqual(info.userPass, null); + test.assertEqual(info.path, "boop"); +}; + +exports.testParseFTP = function(test) { + var info = url.URL("ftp://1.2.3.4/foo"); + test.assertEqual(info.scheme, "ftp"); + test.assertEqual(info.host, "1.2.3.4"); + test.assertEqual(info.port, null); + test.assertEqual(info.userPass, null); + test.assertEqual(info.path, "/foo"); +}; + +exports.testParseFTPWithUserPass = function(test) { + var info = url.URL("ftp://user:pass@1.2.3.4/foo"); + test.assertEqual(info.userPass, "user:pass"); +}; + +exports.testToFilename = function(test) { + test.assertRaises( + function() { url.toFilename("resource://nonexistent"); }, + "resource does not exist: resource://nonexistent/", + "url.toFilename() on nonexistent resources should throw" + ); + + if (!packed) + test.assertMatches(url.toFilename(module.uri), + /.*test-url\.js$/, + "url.toFilename() on resource: URIs should work"); + else + test.assertRaises( + function() { url.toFilename(module.uri); }, + "cannot map to filename: "+module.uri, + "url.toFilename() can fail for packed XPIs"); + + test.assertRaises( + function() { url.toFilename("http://foo.com/"); }, + "cannot map to filename: http://foo.com/", + "url.toFilename() on http: URIs should raise error" + ); + + try { + test.assertMatches( + url.toFilename("chrome://global/content/console.xul"), + /.*console\.xul$/, + "url.toFilename() w/ console.xul works when it maps to filesystem" + ); + } catch (e) { + if (/chrome url isn\'t on filesystem/.test(e.message)) + test.pass("accessing console.xul in jar raises exception"); + else + test.fail("accessing console.xul raises " + e); + } + + // TODO: Are there any chrome URLs that we're certain exist on the + // filesystem? + // test.assertMatches(url.toFilename("chrome://myapp/content/main.js"), + // /.*main\.js$/); +}; + +exports.testFromFilename = function(test) { + var profileDirName = require("sdk/system").pathFor("ProfD"); + var fileUrl = url.fromFilename(profileDirName); + test.assertEqual(url.URL(fileUrl).scheme, 'file', + 'url.toFilename() should return a file: url'); + test.assertEqual(url.fromFilename(url.toFilename(fileUrl)), + fileUrl); +}; + +exports.testURL = function(test) { + let URL = url.URL; + test.assert(URL("h:foo") instanceof URL, "instance is of correct type"); + test.assertRaises(function() URL(), + "malformed URI: undefined", + "url.URL should throw on undefined"); + test.assertRaises(function() URL(""), + "malformed URI: ", + "url.URL should throw on empty string"); + test.assertRaises(function() URL("foo"), + "malformed URI: foo", + "url.URL should throw on invalid URI"); + test.assert(URL("h:foo").scheme, "has scheme"); + test.assertEqual(URL("h:foo").toString(), + "h:foo", + "toString should roundtrip"); + // test relative + base + test.assertEqual(URL("mypath", "http://foo").toString(), + "http://foo/mypath", + "relative URL resolved to base"); + // test relative + no base + test.assertRaises(function() URL("path").toString(), + "malformed URI: path", + "no base for relative URI should throw"); + + let a = URL("h:foo"); + let b = URL(a); + test.assertEqual(b.toString(), + "h:foo", + "a URL can be initialized from another URL"); + test.assertNotStrictEqual(a, b, + "a URL initialized from another URL is not the same object"); + test.assert(a == "h:foo", + "toString is implicit when a URL is compared to a string via =="); + test.assertStrictEqual(a + "", "h:foo", + "toString is implicit when a URL is concatenated to a string"); +}; + +exports.testStringInterface = function(test) { + let URL = url.URL; + var EM = "about:addons"; + var a = URL(EM); + + // make sure the standard URL properties are enumerable and not the String interface bits + test.assertEqual(Object.keys(a), "scheme,userPass,host,port,path", "enumerable key list check for URL."); + test.assertEqual( + JSON.stringify(a), + "{\"scheme\":\"about\",\"userPass\":null,\"host\":null,\"port\":null,\"path\":\"addons\"}", + "JSON.stringify should return a object with correct props and vals."); + + // make sure that the String interface exists and works as expected + test.assertEqual(a.indexOf(":"), EM.indexOf(":"), "indexOf on URL works"); + test.assertEqual(a.valueOf(), EM.valueOf(), "valueOf on URL works."); + test.assertEqual(a.toSource(), EM.toSource(), "toSource on URL works."); + test.assertEqual(a.lastIndexOf("a"), EM.lastIndexOf("a"), "lastIndexOf on URL works."); + test.assertEqual(a.match("t:").toString(), EM.match("t:").toString(), "match on URL works."); + test.assertEqual(a.toUpperCase(), EM.toUpperCase(), "toUpperCase on URL works."); + test.assertEqual(a.toLowerCase(), EM.toLowerCase(), "toLowerCase on URL works."); + test.assertEqual(a.split(":").toString(), EM.split(":").toString(), "split on URL works."); + test.assertEqual(a.charAt(2), EM.charAt(2), "charAt on URL works."); + test.assertEqual(a.charCodeAt(2), EM.charCodeAt(2), "charCodeAt on URL works."); + test.assertEqual(a.concat(EM), EM.concat(a), "concat on URL works."); + test.assertEqual(a.substr(2,3), EM.substr(2,3), "substr on URL works."); + test.assertEqual(a.substring(2,3), EM.substring(2,3), "substring on URL works."); + test.assertEqual(a.trim(), EM.trim(), "trim on URL works."); + test.assertEqual(a.trimRight(), EM.trimRight(), "trimRight on URL works."); + test.assertEqual(a.trimLeft(), EM.trimLeft(), "trimLeft on URL works."); +} + +exports.testDataURLwithouthURI = function (test) { + const { DataURL } = url; + + let dataURL = new DataURL(); + + test.assertEqual(dataURL.base64, false, "base64 is false for empty uri") + test.assertEqual(dataURL.data, "", "data is an empty string for empty uri") + test.assertEqual(dataURL.mimeType, "", "mimeType is an empty string for empty uri") + test.assertEqual(Object.keys(dataURL.parameters).length, 0, "parameters is an empty object for empty uri"); + + test.assertEqual(dataURL.toString(), "data:,"); +} + +exports.testDataURLwithMalformedURI = function (test) { + const { DataURL } = url; + + test.assertRaises(function() { + let dataURL = new DataURL("http://www.mozilla.com/"); + }, + "Malformed Data URL: http://www.mozilla.com/", + "DataURL raises an exception for malformed data uri" + ); +} + +exports.testDataURLparse = function (test) { + const { DataURL } = url; + + let dataURL = new DataURL("data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E"); + + test.assertEqual(dataURL.base64, false, "base64 is false for non base64 data uri") + test.assertEqual(dataURL.data, "<h1>Hello!</h1>", "data is properly decoded") + test.assertEqual(dataURL.mimeType, "text/html", "mimeType is set properly") + test.assertEqual(Object.keys(dataURL.parameters).length, 1, "one parameters specified"); + test.assertEqual(dataURL.parameters["charset"], "US-ASCII", "charset parsed"); + + test.assertEqual(dataURL.toString(), "data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E"); +} + +exports.testDataURLparseBase64 = function (test) { + const { DataURL } = url; + const { decode } = require("sdk/base64"); + + let text = "Awesome!"; + let b64text = "QXdlc29tZSE="; + let dataURL = new DataURL("data:text/plain;base64," + b64text); + + test.assertEqual(dataURL.base64, true, "base64 is true for base64 encoded data uri") + test.assertEqual(dataURL.data, text, "data is properly decoded") + test.assertEqual(dataURL.mimeType, "text/plain", "mimeType is set properly") + test.assertEqual(Object.keys(dataURL.parameters).length, 1, "one parameters specified"); + test.assertEqual(dataURL.parameters["base64"], "", "parameter set without value"); + + test.assertEqual(dataURL.toString(), "data:text/plain;base64," + encodeURIComponent(b64text)); +} diff --git a/tools/addon-sdk-1.12/test/test-uuid.js b/tools/addon-sdk-1.12/test/test-uuid.js new file mode 100644 index 0000000..3c9a4be --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-uuid.js @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { uuid } = require('sdk/util/uuid'); + +exports['test generate uuid'] = function(assert) { + let signature = /{[0-9a-f\-]+}/ + let first = String(uuid()); + let second = String(uuid()); + + assert.ok(signature.test(first), 'first guid has a correct signature'); + assert.ok(signature.test(second), 'second guid has a correct signature'); + assert.notEqual(first, second, 'guid generates new guid on each call'); +}; + +exports['test parse uuid'] = function(assert) { + let firefoxUUID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}'; + let actual = uuid(firefoxUUID); + + assert.equal(actual.number, firefoxUUID, 'uuid parsed given string'); + assert.equal(String(actual), firefoxUUID, 'serializes to the same value'); +}; + +require('test').run(exports); diff --git a/tools/addon-sdk-1.12/test/test-widget.js b/tools/addon-sdk-1.12/test/test-widget.js new file mode 100644 index 0000000..78bc9f9 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-widget.js @@ -0,0 +1,1152 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const {Cc,Ci} = require("chrome"); +const { Loader } = require('sdk/test/loader'); +const widgets = require("sdk/widget"); +const url = require("sdk/url"); +const windowUtils = require("sdk/deprecated/window-utils"); +const tabBrowser = require("sdk/deprecated/tab-browser"); +const timer = require("sdk/timers"); + +exports.testConstructor = function(test) { + test.waitUntilDone(30000); + + let browserWindow = windowUtils.activeBrowserWindow; + let doc = browserWindow.document; + let AddonsMgrListener = browserWindow.AddonsMgrListener; + + function container() doc.getElementById("addon-bar"); + function widgetCount() container() ? container().getElementsByTagName("toolbaritem").length : 0; + let widgetStartCount = widgetCount(); + function widgetNode(index) container() ? container().getElementsByTagName("toolbaritem")[index] : null; + + // Test basic construct/destroy + AddonsMgrListener.onInstalling(); + let w = widgets.Widget({ id: "fooID", label: "foo", content: "bar" }); + AddonsMgrListener.onInstalled(); + test.assertEqual(widgetCount(), widgetStartCount + 1, "panel has correct number of child elements after widget construction"); + + // test widget height + test.assertEqual(widgetNode(0).firstChild.boxObject.height, 16, "widget has correct default height"); + + AddonsMgrListener.onUninstalling(); + w.destroy(); + AddonsMgrListener.onUninstalled(); + w.destroy(); + test.pass("Multiple destroys do not cause an error"); + test.assertEqual(widgetCount(), widgetStartCount, "panel has correct number of child elements after destroy"); + + // Test automatic widget destroy on unload + let loader = Loader(module); + let widgetsFromLoader = loader.require("sdk/widget"); + let widgetStartCount = widgetCount(); + let w = widgetsFromLoader.Widget({ id: "fooID", label: "foo", content: "bar" }); + test.assertEqual(widgetCount(), widgetStartCount + 1, "widget has been correctly added"); + loader.unload(); + test.assertEqual(widgetCount(), widgetStartCount, "widget has been destroyed on module unload"); + + // Test nothing + test.assertRaises( + function() widgets.Widget({}), + "The widget must have a non-empty label property.", + "throws on no properties"); + + // Test no label + test.assertRaises( + function() widgets.Widget({content: "foo"}), + "The widget must have a non-empty label property.", + "throws on no label"); + + // Test empty label + test.assertRaises( + function() widgets.Widget({label: "", content: "foo"}), + "The widget must have a non-empty label property.", + "throws on empty label"); + + // Test no content or image + test.assertRaises( + function() widgets.Widget({id: "fooID", label: "foo"}), + "No content or contentURL property found. Widgets must have one or the other.", + "throws on no content"); + + // Test empty content, no image + test.assertRaises( + function() widgets.Widget({id:"fooID", label: "foo", content: ""}), + "No content or contentURL property found. Widgets must have one or the other.", + "throws on empty content"); + + // Test empty image, no content + test.assertRaises( + function() widgets.Widget({id:"fooID", label: "foo", image: ""}), + "No content or contentURL property found. Widgets must have one or the other.", + "throws on empty content"); + + // Test empty content, empty image + test.assertRaises( + function() widgets.Widget({id:"fooID", label: "foo", content: "", image: ""}), + "No content or contentURL property found. Widgets must have one or the other.", + "throws on empty content"); + + // Test duplicated ID + let duplicateID = widgets.Widget({id: "foo", label: "foo", content: "bar"}); + test.assertRaises( + function() widgets.Widget({id: "foo", label: "bar", content: "bar"}), + /This widget ID is already used:/, + "throws on duplicated id"); + duplicateID.destroy(); + + // Test Bug 652527 + test.assertRaises( + function() widgets.Widget({id: "", label: "bar", content: "bar"}), + /You have to specify a unique value for the id property of/, + "throws on falsey id"); + + // Test duplicate label, different ID + let w1 = widgets.Widget({id: "id1", label: "foo", content: "bar"}); + let w2 = widgets.Widget({id: "id2", label: "foo", content: "bar"}); + w1.destroy(); + w2.destroy(); + + // Test position restore on create/destroy/create + // Create 3 ordered widgets + let w1 = widgets.Widget({id: "first", label:"first", content: "bar"}); + let w2 = widgets.Widget({id: "second", label:"second", content: "bar"}); + let w3 = widgets.Widget({id: "third", label:"third", content: "bar"}); + // Remove the middle widget + test.assertEqual(widgetNode(1).getAttribute("label"), "second", "second widget is the second widget inserted"); + w2.destroy(); + test.assertEqual(widgetNode(1).getAttribute("label"), "third", "second widget is removed, so second widget is now the third one"); + w2 = widgets.Widget({id: "second", label:"second", content: "bar"}); + test.assertEqual(widgetNode(1).getAttribute("label"), "second", "second widget is created again, at the same location"); + // Cleanup this testcase + AddonsMgrListener.onUninstalling(); + w1.destroy(); + w2.destroy(); + w3.destroy(); + AddonsMgrListener.onUninstalled(); + + // Test concurrent widget module instances on addon-bar hiding + let loader = Loader(module); + let anotherWidgetsInstance = loader.require("sdk/widget"); + test.assert(container().collapsed, "UI is hidden when no widgets"); + AddonsMgrListener.onInstalling(); + let w1 = widgets.Widget({id: "foo", label: "foo", content: "bar"}); + // Ideally we would let AddonsMgrListener display the addon bar + // But, for now, addon bar is immediatly displayed by sdk code + // https://bugzilla.mozilla.org/show_bug.cgi?id=627484 + test.assert(!container().collapsed, "UI is already visible when we just added the widget"); + AddonsMgrListener.onInstalled(); + test.assert(!container().collapsed, "UI become visible when we notify AddonsMgrListener about end of addon installation"); + let w2 = anotherWidgetsInstance.Widget({id: "bar", label: "bar", content: "foo"}); + test.assert(!container().collapsed, "UI still visible when we add a second widget"); + AddonsMgrListener.onUninstalling(); + w1.destroy(); + AddonsMgrListener.onUninstalled(); + test.assert(!container().collapsed, "UI still visible when we remove one of two widgets"); + AddonsMgrListener.onUninstalling(); + w2.destroy(); + test.assert(!container().collapsed, "UI is still visible when we have removed all widget but still not called onUninstalled"); + AddonsMgrListener.onUninstalled(); + test.assert(container().collapsed, "UI is hidden when we have removed all widget and called onUninstalled"); + + // Helper for testing a single widget. + // Confirms proper addition and content setup. + function testSingleWidget(widgetOptions) { + // We have to display which test is being run, because here we do not + // use the regular test framework but rather a custom one that iterates + // the `tests` array. + console.info("executing: " + widgetOptions.id); + + let startCount = widgetCount(); + let widget = widgets.Widget(widgetOptions); + let node = widgetNode(startCount); + test.assert(node, "widget node at index"); + test.assertEqual(node.tagName, "toolbaritem", "widget element is correct"); + test.assertEqual(widget.width + "px", node.style.minWidth, "widget width is correct"); + test.assertEqual(widgetCount(), startCount + 1, "container has correct number of child elements"); + let content = node.firstElementChild; + test.assert(content, "found content"); + test.assertMatches(content.tagName, /iframe|image/, "content is iframe or image"); + return widget; + } + + // Array of widgets to test + // and a function to test them. + let tests = []; + function nextTest() { + test.assertEqual(widgetCount(), 0, "widget in last test property cleaned itself up"); + if (!tests.length) + test.done(); + else + timer.setTimeout(tests.shift(), 0); + } + function doneTest() nextTest(); + + // text widget + tests.push(function testTextWidget() testSingleWidget({ + id: "text", + label: "text widget", + content: "oh yeah", + contentScript: "self.postMessage(document.body.innerHTML);", + contentScriptWhen: "end", + onMessage: function (message) { + test.assertEqual(this.content, message, "content matches"); + this.destroy(); + doneTest(); + } + })); + + // html widget + tests.push(function testHTMLWidget() testSingleWidget({ + id: "html", + label: "html widget", + content: "<div>oh yeah</div>", + contentScript: "self.postMessage(document.body.innerHTML);", + contentScriptWhen: "end", + onMessage: function (message) { + test.assertEqual(this.content, message, "content matches"); + this.destroy(); + doneTest(); + } + })); + + // image url widget + tests.push(function testImageURLWidget() testSingleWidget({ + id: "image", + label: "image url widget", + contentURL: require("sdk/self").data.url("test.html"), + contentScript: "self.postMessage({title: document.title, " + + "tag: document.body.firstElementChild.tagName, " + + "content: document.body.firstElementChild.innerHTML});", + contentScriptWhen: "end", + onMessage: function (message) { + test.assertEqual(message.title, "foo", "title matches"); + test.assertEqual(message.tag, "P", "element matches"); + test.assertEqual(message.content, "bar", "element content matches"); + this.destroy(); + doneTest(); + } + })); + + // web uri widget + tests.push(function testWebURIWidget() testSingleWidget({ + id: "web", + label: "web uri widget", + contentURL: require("sdk/self").data.url("test.html"), + contentScript: "self.postMessage({title: document.title, " + + "tag: document.body.firstElementChild.tagName, " + + "content: document.body.firstElementChild.innerHTML});", + contentScriptWhen: "end", + onMessage: function (message) { + test.assertEqual(message.title, "foo", "title matches"); + test.assertEqual(message.tag, "P", "element matches"); + test.assertEqual(message.content, "bar", "element content matches"); + this.destroy(); + doneTest(); + } + })); + + // event: onclick + content + tests.push(function testOnclickEventContent() testSingleWidget({ + id: "click", + label: "click test widget - content", + content: "<div id='me'>foo</div>", + contentScript: "var evt = document.createEvent('HTMLEvents'); " + + "evt.initEvent('click', true, true ); " + + "document.getElementById('me').dispatchEvent(evt);", + contentScriptWhen: "end", + onClick: function() { + test.pass("onClick called"); + this.destroy(); + doneTest(); + } + })); + + // event: onmouseover + content + tests.push(function testOnmouseoverEventContent() testSingleWidget({ + id: "mouseover", + label: "mouseover test widget - content", + content: "<div id='me'>foo</div>", + contentScript: "var evt = document.createEvent('HTMLEvents'); " + + "evt.initEvent('mouseover', true, true ); " + + "document.getElementById('me').dispatchEvent(evt);", + contentScriptWhen: "end", + onMouseover: function() { + test.pass("onMouseover called"); + this.destroy(); + doneTest(); + } + })); + + // event: onmouseout + content + tests.push(function testOnmouseoutEventContent() testSingleWidget({ + id: "mouseout", + label: "mouseout test widget - content", + content: "<div id='me'>foo</div>", + contentScript: "var evt = document.createEvent('HTMLEvents'); " + + "evt.initEvent('mouseout', true, true ); " + + "document.getElementById('me').dispatchEvent(evt);", + contentScriptWhen: "end", + onMouseout: function() { + test.pass("onMouseout called"); + this.destroy(); + doneTest(); + } + })); + + // event: onclick + image + tests.push(function testOnclickEventImage() testSingleWidget({ + id: "click", + label: "click test widget - image", + contentURL: require("sdk/self").data.url("moz_favicon.ico"), + contentScript: "var evt = document.createEvent('HTMLEvents'); " + + "evt.initEvent('click', true, true ); " + + "document.body.firstElementChild.dispatchEvent(evt);", + contentScriptWhen: "end", + onClick: function() { + test.pass("onClick called"); + this.destroy(); + doneTest(); + } + })); + + // event: onmouseover + image + tests.push(function testOnmouseoverEventImage() testSingleWidget({ + id: "mouseover", + label: "mouseover test widget - image", + contentURL: require("sdk/self").data.url("moz_favicon.ico"), + contentScript: "var evt = document.createEvent('HTMLEvents'); " + + "evt.initEvent('mouseover', true, true ); " + + "document.body.firstElementChild.dispatchEvent(evt);", + contentScriptWhen: "end", + onMouseover: function() { + test.pass("onMouseover called"); + this.destroy(); + doneTest(); + } + })); + + // event: onmouseout + image + tests.push(function testOnmouseoutEventImage() testSingleWidget({ + id: "mouseout", + label: "mouseout test widget - image", + contentURL: require("sdk/self").data.url("moz_favicon.ico"), + contentScript: "var evt = document.createEvent('HTMLEvents'); " + + "evt.initEvent('mouseout', true, true ); " + + "document.body.firstElementChild.dispatchEvent(evt);", + contentScriptWhen: "end", + onMouseout: function() { + test.pass("onMouseout called"); + this.destroy(); + doneTest(); + } + })); + + // test multiple widgets + tests.push(function testMultipleWidgets() { + let w1 = widgets.Widget({id: "first", label: "first widget", content: "first content"}); + let w2 = widgets.Widget({id: "second", label: "second widget", content: "second content"}); + + w1.destroy(); + w2.destroy(); + + doneTest(); + }); + + // test updating widget content + let loads = 0; + tests.push(function testUpdatingWidgetContent() testSingleWidget({ + id: "content", + label: "content update test widget", + content: "<div id='me'>foo</div>", + contentScript: "self.postMessage(1)", + contentScriptWhen: "ready", + onMessage: function(message) { + if (!this.flag) { + this.content = "<div id='me'>bar</div>"; + this.flag = 1; + } + else { + test.assertEqual(this.content, "<div id='me'>bar</div>"); + this.destroy(); + doneTest(); + } + } + })); + + // test updating widget contentURL + let url1 = "data:text/html;charset=utf-8,<body>foodle</body>"; + let url2 = "data:text/html;charset=utf-8,<body>nistel</body>"; + + tests.push(function testUpdatingContentURL() testSingleWidget({ + id: "content", + label: "content update test widget", + contentURL: url1, + contentScript: "self.postMessage(document.location.href);", + contentScriptWhen: "end", + onMessage: function(message) { + if (!this.flag) { + test.assertEqual(this.contentURL.toString(), url1); + test.assertEqual(message, url1); + this.contentURL = url2; + this.flag = 1; + } + else { + test.assertEqual(this.contentURL.toString(), url2); + test.assertEqual(message, url2); + this.destroy(); + doneTest(); + } + } + })); + + // test tooltip + tests.push(function testTooltip() testSingleWidget({ + id: "text", + label: "text widget", + content: "oh yeah", + tooltip: "foo", + contentScript: "self.postMessage(1)", + contentScriptWhen: "ready", + onMessage: function(message) { + test.assertEqual(this.tooltip, "foo", "tooltip matches"); + this.destroy(); + doneTest(); + } + })); + + // test tooltip fallback to label + tests.push(function testTooltipFallback() testSingleWidget({ + id: "fallback", + label: "fallback", + content: "oh yeah", + contentScript: "self.postMessage(1)", + contentScriptWhen: "ready", + onMessage: function(message) { + test.assertEqual(this.tooltip, this.label, "tooltip fallbacks to label"); + this.destroy(); + doneTest(); + } + })); + + // test updating widget tooltip + let updated = false; + tests.push(function testUpdatingTooltip() testSingleWidget({ + id: "tooltip", + label: "tooltip update test widget", + tooltip: "foo", + content: "<div id='me'>foo</div>", + contentScript: "self.postMessage(1)", + contentScriptWhen: "ready", + onMessage: function(message) { + this.tooltip = "bar"; + test.assertEqual(this.tooltip, "bar", "tooltip gets updated"); + this.destroy(); + doneTest(); + } + })); + + // test allow attribute + tests.push(function testDefaultAllow() testSingleWidget({ + id: "allow", + label: "allow.script attribute", + content: "<script>document.title = 'ok';</script>", + contentScript: "self.postMessage(document.title)", + onMessage: function(message) { + test.assertEqual(message, "ok", "scripts are evaluated by default"); + this.destroy(); + doneTest(); + } + })); + + tests.push(function testExplicitAllow() testSingleWidget({ + id: "allow", + label: "allow.script attribute", + allow: {script: true}, + content: "<script>document.title = 'ok';</script>", + contentScript: "self.postMessage(document.title)", + onMessage: function(message) { + test.assertEqual(message, "ok", "scripts are evaluated when we want to"); + this.destroy(); + doneTest(); + } + })); + + tests.push(function testExplicitDisallow() testSingleWidget({ + id: "allow", + label: "allow.script attribute", + content: "<script>document.title = 'ok';</script>", + allow: {script: false}, + contentScript: "self.postMessage(document.title)", + onMessage: function(message) { + test.assertNotEqual(message, "ok", "scripts aren't evaluated when " + + "explicitly blocked it"); + this.destroy(); + doneTest(); + } + })); + + // test multiple windows + tests.push(function testMultipleWindows() { + tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) { + let browserWindow = e.target.defaultView; + let doc = browserWindow.document; + function container() doc.getElementById("addon-bar"); + function widgetCount2() container() ? container().childNodes.length : 0; + let widgetStartCount2 = widgetCount2(); + + let w1Opts = {id:"first", label: "first widget", content: "first content"}; + let w1 = testSingleWidget(w1Opts); + test.assertEqual(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after first widget"); + + let w2Opts = {id:"second", label: "second widget", content: "second content"}; + let w2 = testSingleWidget(w2Opts); + test.assertEqual(widgetCount2(), widgetStartCount2 + 2, "2nd window has correct number of child elements after second widget"); + + w1.destroy(); + test.assertEqual(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after first destroy"); + w2.destroy(); + test.assertEqual(widgetCount2(), widgetStartCount2, "2nd window has correct number of child elements after second destroy"); + + closeBrowserWindow(browserWindow, function() { + doneTest(); + }); + }}); + }); + + // test window closing + tests.push(function testWindowClosing() { + // 1/ Create a new widget + let w1Opts = { + id:"first", + label: "first widget", + content: "first content", + contentScript: "self.port.on('event', function () self.port.emit('event'))" + }; + let widget = testSingleWidget(w1Opts); + let windows = require("sdk/windows").browserWindows; + + // 2/ Retrieve a WidgetView for the initial browser window + let acceptDetach = false; + let mainView = widget.getView(windows.activeWindow); + test.assert(mainView, "Got first widget view"); + mainView.on("detach", function () { + // 8/ End of our test. Accept detach event only when it occurs after + // widget.destroy() + if (acceptDetach) + doneTest(); + else + test.fail("View on initial window should not be destroyed"); + }); + mainView.port.on("event", function () { + // 7/ Receive event sent during 6/ and cleanup our test + acceptDetach = true; + widget.destroy(); + }); + + // 3/ First: open a new browser window + windows.open({ + url: "about:blank", + onOpen: function(window) { + // 4/ Retrieve a WidgetView for this new window + let view = widget.getView(window); + test.assert(view, "Got second widget view"); + view.port.on("event", function () { + test.fail("We should not receive event on the detach view"); + }); + view.on("detach", function () { + // The related view is destroyed + // 6/ Send a custom event + test.assertRaises(function () { + view.port.emit("event"); + }, + /The widget has been destroyed and can no longer be used./, + "emit on a destroyed view should throw"); + widget.port.emit("event"); + }); + + // 5/ Destroy this window + window.close(); + } + }); + }); + + tests.push(function testAddonBarHide() { + // Hide the addon-bar + browserWindow.setToolbarVisibility(container(), false); + test.assert(container().collapsed, + "1st window starts with an hidden addon-bar"); + + // Then open a browser window and verify that the addon-bar remains hidden + tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) { + let browserWindow2 = e.target.defaultView; + let doc2 = browserWindow2.document; + function container2() doc2.getElementById("addon-bar"); + function widgetCount2() container2() ? container2().childNodes.length : 0; + let widgetStartCount2 = widgetCount2(); + test.assert(container2().collapsed, + "2nd window starts with an hidden addon-bar"); + + let w1Opts = {id:"first", label: "first widget", content: "first content"}; + let w1 = testSingleWidget(w1Opts); + test.assertEqual(widgetCount2(), widgetStartCount2 + 1, + "2nd window has correct number of child elements after" + + "widget creation"); + w1.destroy(); + test.assertEqual(widgetCount2(), widgetStartCount2, + "2nd window has correct number of child elements after" + + "widget destroy"); + + test.assert(container().collapsed, "1st window has an hidden addon-bar"); + test.assert(container2().collapsed, "2nd window has an hidden addon-bar"); + + // Reset addon-bar visibility before exiting this test + browserWindow.setToolbarVisibility(container(), true); + + closeBrowserWindow(browserWindow2, function() { + doneTest(); + }); + }}); + }); + + // test widget.width + tests.push(function testWidgetWidth() testSingleWidget({ + id: "text", + label: "test widget.width", + content: "test width", + width: 200, + contentScript: "self.postMessage(1)", + contentScriptWhen: "ready", + onMessage: function(message) { + test.assertEqual(this.width, 200); + + let node = widgetNode(0); + test.assertEqual(this.width, node.style.minWidth.replace("px", "")); + test.assertEqual(this.width, node.firstElementChild.style.width.replace("px", "")); + this.width = 300; + test.assertEqual(this.width, node.style.minWidth.replace("px", "")); + test.assertEqual(this.width, node.firstElementChild.style.width.replace("px", "")); + + this.destroy(); + doneTest(); + } + })); + + // test click handler not respond to right-click + let clickCount = 0; + tests.push(function testNoRightClick() testSingleWidget({ + id: "click-content", + label: "click test widget - content", + content: "<div id='me'>foo</div>", + contentScript: "var evt = document.createEvent('MouseEvents'); " + + "evt.initMouseEvent('click', true, true, window, " + + " 0, 0, 0, 0, 0, false, false, false, false, 2, null); " + + "document.getElementById('me').dispatchEvent(evt); " + + "evt = document.createEvent('HTMLEvents'); " + + "evt.initEvent('click', true, true ); " + + "document.getElementById('me').dispatchEvent(evt); " + + "evt = document.createEvent('HTMLEvents'); " + + "evt.initEvent('mouseover', true, true ); " + + "document.getElementById('me').dispatchEvent(evt);", + contentScriptWhen: "end", + onClick: function() clickCount++, + onMouseover: function() { + test.assertEqual(clickCount, 1, "right click wasn't sent to click handler"); + this.destroy(); + doneTest(); + } + })); + + // kick off test execution + doneTest(); +}; + +exports.testPanelWidget1 = function testPanelWidget1(test) { + const widgets = require("sdk/widget"); + + let widget1 = widgets.Widget({ + id: "panel1", + label: "panel widget 1", + content: "<div id='me'>foo</div>", + contentScript: "var evt = document.createEvent('HTMLEvents'); " + + "evt.initEvent('click', true, true ); " + + "document.body.dispatchEvent(evt);", + contentScriptWhen: "end", + panel: require("sdk/panel").Panel({ + contentURL: "data:text/html;charset=utf-8,<body>Look ma, a panel!</body>", + onShow: function() { + widget1.destroy(); + test.pass("panel displayed on click"); + test.done(); + } + }) + }); + test.waitUntilDone(); +}; + +exports.testPanelWidget2 = function testPanelWidget2(test) { + const widgets = require("sdk/widget"); + test.assertRaises( + function() { + widgets.Widget({ + id: "panel2", + label: "panel widget 2", + panel: {} + }); + }, + "The option \"panel\" must be one of the following types: null, undefined, object", + "widget.panel must be a Panel object" + ); +}; + +exports.testPanelWidget3 = function testPanelWidget3(test) { + const widgets = require("sdk/widget"); + let onClickCalled = false; + let widget3 = widgets.Widget({ + id: "panel3", + label: "panel widget 3", + content: "<div id='me'>foo</div>", + contentScript: "var evt = document.createEvent('HTMLEvents'); " + + "evt.initEvent('click', true, true ); " + + "document.body.firstElementChild.dispatchEvent(evt);", + contentScriptWhen: "end", + onClick: function() { + onClickCalled = true; + this.panel.show(); + }, + panel: require("sdk/panel").Panel({ + contentURL: "data:text/html;charset=utf-8,<body>Look ma, a panel!</body>", + onShow: function() { + test.assert( + onClickCalled, + "onClick called on click for widget with both panel and onClick" + ); + widget3.destroy(); + test.done(); + } + }) + }); + test.waitUntilDone(); +}; + +exports.testWidgetMessaging = function testWidgetMessaging(test) { + test.waitUntilDone(); + let origMessage = "foo"; + const widgets = require("sdk/widget"); + let widget = widgets.Widget({ + id: "foo", + label: "foo", + content: "<bar>baz</bar>", + contentScriptWhen: "end", + contentScript: "self.on('message', function(data) { self.postMessage(data); }); self.postMessage('ready');", + onMessage: function(message) { + if (message == "ready") + widget.postMessage(origMessage); + else { + test.assertEqual(origMessage, message); + widget.destroy(); + test.done(); + } + } + }); +}; + +exports.testWidgetViews = function testWidgetViews(test) { + test.waitUntilDone(); + const widgets = require("sdk/widget"); + let widget = widgets.Widget({ + id: "foo", + label: "foo", + content: "<bar>baz</bar>", + contentScriptWhen: "ready", + contentScript: "self.on('message', function(data) self.postMessage(data)); self.postMessage('ready')", + onAttach: function(view) { + test.pass("WidgetView created"); + view.on("message", function () { + test.pass("Got message in WidgetView"); + widget.destroy(); + }); + view.on("detach", function () { + test.pass("WidgetView destroyed"); + test.done(); + }); + } + }); + +}; + +exports.testWidgetViewsUIEvents = function testWidgetViewsUIEvents(test) { + test.waitUntilDone(); + const widgets = require("sdk/widget"); + let view = null; + let widget = widgets.Widget({ + id: "foo", + label: "foo", + content: "<div id='me'>foo</div>", + contentScript: "var evt = document.createEvent('HTMLEvents'); " + + "evt.initEvent('click', true, true ); " + + "document.getElementById('me').dispatchEvent(evt);", + contentScriptWhen: "ready", + onAttach: function(attachView) { + view = attachView; + test.pass("Got attach event"); + }, + onClick: function (eventView) { + test.assertEqual(view, eventView, + "event first argument is equal to the WidgetView"); + let view2 = widget.getView(require("sdk/windows").browserWindows.activeWindow); + test.assertEqual(view, view2, + "widget.getView return the same WidgetView"); + widget.destroy(); + test.done(); + } + }); +}; + +exports.testWidgetViewsCustomEvents = function testWidgetViewsCustomEvents(test) { + test.waitUntilDone(); + const widgets = require("sdk/widget"); + let widget = widgets.Widget({ + id: "foo", + label: "foo", + content: "<div id='me'>foo</div>", + contentScript: "self.port.emit('event', 'ok');", + contentScriptWhen: "ready", + onAttach: function(view) { + view.port.on("event", function (data) { + test.assertEqual(data, "ok", + "event argument is valid on WidgetView"); + }); + }, + }); + widget.port.on("event", function (data) { + test.assertEqual(data, "ok", + "event argument is valid on Widget"); + widget.destroy(); + test.done(); + }); +}; + +exports.testWidgetViewsTooltip = function testWidgetViewsTooltip(test) { + test.waitUntilDone(); + const widgets = require("sdk/widget"); + + let widget = new widgets.Widget({ + id: "foo", + label: "foo", + content: "foo" + }); + let view = widget.getView(require("sdk/windows").browserWindows.activeWindow); + widget.tooltip = null; + test.assertEqual(view.tooltip, "foo", + "view tooltip defaults to base widget label"); + test.assertEqual(widget.tooltip, "foo", + "tooltip defaults to base widget label"); + widget.destroy(); + test.done(); +}; + +exports.testWidgetMove = function testWidgetMove(test) { + test.waitUntilDone(); + + let windowUtils = require("sdk/deprecated/window-utils"); + let widgets = require("sdk/widget"); + + let browserWindow = windowUtils.activeBrowserWindow; + let doc = browserWindow.document; + + let label = "unique-widget-label"; + let origMessage = "message after node move"; + let gotFirstReady = false; + + let widget = widgets.Widget({ + id: "foo", + label: label, + content: "<bar>baz</bar>", + contentScriptWhen: "ready", + contentScript: "self.on('message', function(data) { self.postMessage(data); }); self.postMessage('ready');", + onMessage: function(message) { + if (message == "ready") { + if (!gotFirstReady) { + test.pass("Got first ready event"); + let widgetNode = doc.querySelector('toolbaritem[label="' + label + '"]'); + let parent = widgetNode.parentNode; + parent.insertBefore(widgetNode, parent.firstChild); + gotFirstReady = true; + } else { + test.pass("Got second ready event"); + widget.postMessage(origMessage); + } + } + else { + test.assertEqual(origMessage, message, "Got message after node move"); + widget.destroy(); + test.done(); + } + } + }); +}; + +/* +The bug is exhibited when a widget with HTML content has it's content +changed to new HTML content with a pound in it. Because the src of HTML +content is converted to a data URI, the underlying iframe doesn't +consider the content change a navigation change, so doesn't load +the new content. +*/ +exports.testWidgetWithPound = function testWidgetWithPound(test) { + test.waitUntilDone(); + + function getWidgetContent(widget) { + let windowUtils = require("sdk/deprecated/window-utils"); + let browserWindow = windowUtils.activeBrowserWindow; + let doc = browserWindow.document; + let widgetNode = doc.querySelector('toolbaritem[label="' + widget.label + '"]'); + test.assert(widgetNode, 'found widget node in the front-end'); + return widgetNode.firstChild.contentDocument.body.innerHTML; + } + + let widgets = require("sdk/widget"); + let count = 0; + let widget = widgets.Widget({ + id: "1", + label: "foo", + content: "foo", + contentScript: "window.addEventListener('load', self.postMessage, false);", + onMessage: function() { + count++; + if (count == 1) { + widget.content = "foo#"; + } + else { + test.assertEqual(getWidgetContent(widget), "foo#", "content updated to pound?"); + widget.destroy(); + test.done(); + } + } + }); +}; + +exports.testContentScriptOptionsOption = function(test) { + test.waitUntilDone(); + + let widget = require("sdk/widget").Widget({ + id: "fooz", + label: "fooz", + content: "fooz", + contentScript: "self.postMessage( [typeof self.options.d, self.options] );", + contentScriptWhen: "end", + contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}}, + onMessage: function(msg) { + test.assertEqual( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' ); + test.assertEqual( typeof msg[1], 'object', 'object as contentScriptOptions' ); + test.assertEqual( msg[1].a, true, 'boolean in contentScriptOptions' ); + test.assertEqual( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' ); + test.assertEqual( msg[1].c, 'string', 'string in contentScriptOptions' ); + widget.destroy(); + test.done(); + } + }); +}; + +exports.testOnAttachWithoutContentScript = function(test) { + test.waitUntilDone(); + + let widget = require("sdk/widget").Widget({ + id: "onAttachNoCS", + label: "onAttachNoCS", + content: "onAttachNoCS", + onAttach: function (view) { + test.pass("received attach event"); + widget.destroy(); + test.done(); + } + }); +}; + +exports.testPostMessageOnAttach = function(test) { + test.waitUntilDone(); + + let widget = require("sdk/widget").Widget({ + id: "onAttach", + label: "onAttach", + content: "onAttach", + // 1) Send a message immediatly after `attach` event + onAttach: function (view) { + view.postMessage("ok"); + }, + // 2) Listen to it and forward it back to the widget + contentScript: "self.on('message', self.postMessage);", + // 3) Listen to this forwarded message + onMessage: function (msg) { + test.assertEqual( msg, "ok", "postMessage works on `attach` event"); + widget.destroy(); + test.done(); + } + }); +}; + +exports.testPostMessageOnLocationChange = function(test) { + test.waitUntilDone(); + + let attachEventCount = 0; + let messagesCount = 0; + let widget = require("sdk/widget").Widget({ + id: "onLocationChange", + label: "onLocationChange", + content: "onLocationChange", + contentScript: "new " + function ContentScriptScope() { + // Emit an event when content script is applied in order to know when + // the first document is loaded so that we can load the 2nd one + self.postMessage("ready"); + // And forward any incoming message back to the widget to see if + // messaging is working on 2nd document + self.on("message", self.postMessage); + }, + onMessage: function (msg) { + messagesCount++; + if (messagesCount == 1) { + test.assertEqual(msg, "ready", "First document is loaded"); + widget.content = "location changed"; + } + else if (messagesCount == 2) { + test.assertEqual(msg, "ready", "Second document is loaded"); + widget.postMessage("ok"); + } + else if (messagesCount == 3) { + test.assertEqual(msg, "ok", + "We receive the message sent to the 2nd document"); + widget.destroy(); + test.done(); + } + } + }); +}; + +exports.testNavigationBarWidgets = function testNavigationBarWidgets(test) { + test.waitUntilDone(); + + let w1 = widgets.Widget({id: "1st", label: "1st widget", content: "1"}); + let w2 = widgets.Widget({id: "2nd", label: "2nd widget", content: "2"}); + let w3 = widgets.Widget({id: "3rd", label: "3rd widget", content: "3"}); + + // First wait for all 3 widgets to be added to the current browser window + let firstAttachCount = 0; + function onAttachFirstWindow(widget) { + if (++firstAttachCount<3) + return; + onWidgetsReady(); + } + w1.once("attach", onAttachFirstWindow); + w2.once("attach", onAttachFirstWindow); + w3.once("attach", onAttachFirstWindow); + + function getWidgetNode(toolbar, position) { + return toolbar.getElementsByTagName("toolbaritem")[position]; + } + function openBrowserWindow() { + let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Ci.nsIWindowWatcher); + let urlString = Cc["@mozilla.org/supports-string;1"]. + createInstance(Ci.nsISupportsString); + urlString.data = "about:blank"; + return ww.openWindow(null, "chrome://browser/content/browser.xul", + "_blank", "chrome,all,dialog=no", urlString); + } + + // Then move them before openeing a new browser window + function onWidgetsReady() { + // Hack to move 2nd and 3rd widgets manually to the navigation bar right after + // the search box. + let browserWindow = windowUtils.activeBrowserWindow; + let doc = browserWindow.document; + let addonBar = doc.getElementById("addon-bar"); + let w2ToolbarItem = getWidgetNode(addonBar, 1); + let w3ToolbarItem = getWidgetNode(addonBar, 2); + let navBar = doc.getElementById("nav-bar"); + let searchBox = doc.getElementById("search-container"); + // Insert 3rd at the right of search box by adding it before its right sibling + navBar.insertItem(w3ToolbarItem.id, searchBox.nextSibling, null, false); + // Then insert 2nd before 3rd + navBar.insertItem(w2ToolbarItem.id, w3ToolbarItem, null, false); + // Widget and Firefox codes rely on this `currentset` attribute, + // so ensure it is correctly saved + navBar.setAttribute("currentset", navBar.currentSet); + doc.persist(navBar.id, "currentset"); + // Update addonbar too as we removed widget from there. + // Otherwise, widgets may still be added to this toolbar. + addonBar.setAttribute("currentset", addonBar.currentSet); + doc.persist(addonBar.id, "currentset"); + + // Wait for all widget to be attached to this new window before checking + // their position + let attachCount = 0; + let browserWindow2; + function onAttach(widget) { + if (++attachCount < 3) + return; + let doc = browserWindow2.document; + let addonBar = doc.getElementById("addon-bar"); + let searchBox = doc.getElementById("search-container"); + + // Ensure that 1st is in addon bar + test.assertEqual(getWidgetNode(addonBar, 0).getAttribute("label"), w1.label); + // And that 2nd and 3rd keep their original positions in navigation bar, + // i.e. right after search box + test.assertEqual(searchBox.nextSibling.getAttribute("label"), w2.label); + test.assertEqual(searchBox.nextSibling.nextSibling.getAttribute("label"), w3.label); + + w1.destroy(); + w2.destroy(); + w3.destroy(); + + closeBrowserWindow(browserWindow2, function() { + test.done(); + }); + } + w1.on("attach", onAttach); + w2.on("attach", onAttach); + w3.on("attach", onAttach); + + browserWindow2 = openBrowserWindow(browserWindow); + } +}; + +/******************* helpers *********************/ + +// Helper for calling code at window close +function closeBrowserWindow(window, callback) { + timer.setTimeout(function() { + window.addEventListener("unload", function onUnload() { + window.removeEventListener("unload", onUnload, false); + callback(); + }, false); + window.close(); + }, 0); +} + +// ADD NO TESTS BELOW THIS LINE! /////////////////////////////////////////////// + +// If the module doesn't support the app we're being run in, require() will +// throw. In that case, remove all tests above from exports, and add one dummy +// test that passes. +try { + require("sdk/widget"); +} +catch (err) { + // This bug should be mentioned in the error message. + let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=560716"; + if (err.message.indexOf(bug) < 0) + throw err; + + module.exports = { + testAppNotSupported: function (test) { + test.pass("the widget module does not support this application."); + } + } +} + diff --git a/tools/addon-sdk-1.12/test/test-window-loader.js b/tools/addon-sdk-1.12/test/test-window-loader.js new file mode 100644 index 0000000..b8fecce --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-window-loader.js @@ -0,0 +1,120 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const { WindowLoader } = require('sdk/windows/loader'), + { Trait } = require('sdk/deprecated/traits'); + +const Loader = Trait.compose( + WindowLoader, + { + constructor: function Loader(options) { + this._onLoad = options.onLoad; + this._onUnload = options.onUnload; + if ('window' in options) + this._window = options.window; + this._load(); + this.window = this._window; + }, + window: null, + _onLoad: null, + _onUnload: null, + _tabOptions: [] + } +); + +exports['test compositions with missing required properties'] = function(test) { + test.assertRaises( + function() WindowLoader.compose({})(), + 'Missing required property: _onLoad', + 'should throw missing required property exception' + ); + test.assertRaises( + function() WindowLoader.compose({ _onLoad: null, _tabOptions: null })(), + 'Missing required property: _onUnload', + 'should throw missing required property `_onUnload`' + ); + test.assertRaises( + function() WindowLoader.compose({ _onUnload: null, _tabOptions: null })(), + 'Missing required property: _onLoad', + 'should throw missing required property `_onLoad`' + ); + test.assertRaises( + function() WindowLoader.compose({ _onUnload: null, _onLoad: null })(), + 'Missing required property: _tabOptions', + 'should throw missing required property `_tabOptions`' + ); +}; + +exports['test `load` events'] = function(test) { + test.waitUntilDone(); + let onLoadCalled = false; + Loader({ + onLoad: function(window) { + onLoadCalled = true; + test.assertEqual( + window, this._window, 'windows should match' + ); + test.assertEqual( + window.document.readyState, 'complete', 'window must be fully loaded' + ); + window.close(); + }, + onUnload: function(window) { + test.assertEqual( + window, this._window, 'windows should match' + ); + test.assertEqual( + window.document.readyState, 'complete', 'window must be fully loaded' + ); + test.assert(onLoadCalled, 'load callback is supposed to be called'); + test.done(); + } + }); +}; + +exports['test removeing listeners'] = function(test) { + test.waitUntilDone(); + Loader({ + onLoad: function(window) { + test.assertEqual( + window, this._window, 'windows should match' + ); + window.close(); + }, + onUnload: function(window) { + test.done(); + } + }); +}; + +exports['test create loader from opened window'] = function(test) { + test.waitUntilDone(); + let onUnloadCalled = false; + Loader({ + onLoad: function(window) { + test.assertEqual( + window, this._window, 'windows should match' + ); + test.assertEqual( + window.document.readyState, 'complete', 'window must be fully loaded' + ); + Loader({ + window: window, + onLoad: function(win) { + test.assertEqual(win, window, 'windows should match'); + window.close(); + }, + onUnload: function(window) { + test.assert(onUnloadCalled, 'first handler should be called already'); + test.done(); + } + }); + }, + onUnload: function(window) { + onUnloadCalled = true; + } + }); +}; + diff --git a/tools/addon-sdk-1.12/test/test-window-observer.js b/tools/addon-sdk-1.12/test/test-window-observer.js new file mode 100644 index 0000000..4a6264a --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-window-observer.js @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Loader } = require("sdk/test/loader"); +const timer = require("sdk/timers"); + +exports["test unload window observer"] = function(assert, done) { + // Hacky way to be able to create unloadable modules via makeSandboxedLoader. + let loader = Loader(module); + + let utils = loader.require("sdk/deprecated/window-utils"); + let { activeBrowserWindow: activeWindow } = utils; + let { isBrowser } = require('sdk/window/utils'); + let observer = loader.require("sdk/windows/observer").observer; + let opened = 0; + let closed = 0; + + observer.on("open", function onOpen(window) { + // Ignoring non-browser windows + if (isBrowser(window)) + opened++; + }); + observer.on("close", function onClose(window) { + // Ignore non-browser windows & already opened `activeWindow` (unload will + // emit close on it even though it is not actually closed). + if (isBrowser(window) && window !== activeWindow) + closed++; + }); + + // Open window and close it to trigger observers. + activeWindow.open().close(); + + // Unload the module so that all listeners set by observer are removed. + loader.unload(); + + // Open and close window once again. + activeWindow.open().close(); + + // Enqueuing asserts to make sure that assertion is not performed early. + timer.setTimeout(function () { + assert.equal(1, opened, "observer open was called before unload only"); + assert.equal(1, closed, "observer close was called before unload only"); + done(); + }, 0); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-window-utils.js b/tools/addon-sdk-1.12/test/test-window-utils.js new file mode 100644 index 0000000..c93b0d4 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-window-utils.js @@ -0,0 +1,350 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var windowUtils = require("sdk/deprecated/window-utils"); +var timer = require("sdk/timers"); +var { Cc, Ci } = require("chrome"); +var { Loader, unload } = require("sdk/test/loader"); + +function toArray(iterator) { + let array = []; + for each (let item in iterator) + array.push(item); + return array; +} + +function makeEmptyWindow() { + var xulNs = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + var blankXul = ('<?xml version="1.0"?>' + + '<?xml-stylesheet href="chrome://global/skin/" ' + + ' type="text/css"?>' + + '<window xmlns="' + xulNs + '" windowtype="test:window">' + + '</window>'); + var url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," + escape(blankXul); + var features = ["chrome", "width=10", "height=10"]; + + var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"] + .getService(Ci.nsIWindowWatcher); + return ww.openWindow(null, url, null, features.join(","), null); +} + +exports['test close on unload'] = function(assert) { + var timesClosed = 0; + var fakeWindow = { + _listeners: [], + addEventListener: function(name, func, bool) { + this._listeners.push(func); + }, + removeEventListener: function(name, func, bool) { + var index = this._listeners.indexOf(func); + if (index == -1) + throw new Error("event listener not found"); + this._listeners.splice(index, 1); + }, + close: function() { + timesClosed++; + this._listeners.forEach( + function(func) { + func({target: fakeWindow.document}); + }); + }, + document: { + get defaultView() { return fakeWindow; } + } + }; + + let loader = Loader(module); + loader.require("sdk/deprecated/window-utils").closeOnUnload(fakeWindow); + assert.equal(fakeWindow._listeners.length, 1, + "unload listener added on closeOnUnload()"); + assert.equal(timesClosed, 0, + "window not closed when registered."); + loader.unload(); + assert.equal(timesClosed, 1, + "window closed on module unload."); + assert.equal(fakeWindow._listeners.length, 0, + "unload event listener removed on module unload"); + + timesClosed = 0; + loader = Loader(module); + loader.require("sdk/deprecated/window-utils").closeOnUnload(fakeWindow); + assert.equal(timesClosed, 0, + "window not closed when registered."); + fakeWindow.close(); + assert.equal(timesClosed, 1, + "window closed when close() called."); + assert.equal(fakeWindow._listeners.length, 0, + "unload event listener removed on window close"); + loader.unload(); + assert.equal(timesClosed, 1, + "window not closed again on module unload."); +}; + +exports['test window watcher'] = function(assert, done) { + var myWindow; + var finished = false; + + var delegate = { + onTrack: function(window) { + if (window == myWindow) { + assert.pass("onTrack() called with our test window"); + timer.setTimeout(function() { myWindow.close(); }, 1); + } + }, + onUntrack: function(window) { + if (window == myWindow) { + assert.pass("onUntrack() called with our test window"); + timer.setTimeout(function() { + if (!finished) { + finished = true; + myWindow = null; + wt.unload(); + done(); + } else + assert.fail("finishTest() called multiple times."); + }, 1); + } + } + }; + + // test bug 638007 (new is optional), using new + var wt = new windowUtils.WindowTracker(delegate); + myWindow = makeEmptyWindow(); +}; + +exports['test window watcher untracker'] = function(assert, done) { + var myWindow; + var tracks = 0; + var unloadCalled = false; + + var delegate = { + onTrack: function(window) { + tracks = tracks + 1; + if (window == myWindow) { + assert.pass("onTrack() called with our test window"); + timer.setTimeout(function() { + myWindow.close(); + }, 1); + } + }, + onUntrack: function(window) { + tracks = tracks - 1; + if (window == myWindow && !unloadCalled) { + unloadCalled = true; + timer.setTimeout(function() { + wt.unload(); + }, 1); + } + if (0 > tracks) { + assert.fail("WindowTracker onUntrack was called more times than onTrack.."); + } + else if (0 == tracks) { + timer.setTimeout(function() { + myWindow = null; + done(); + }, 1); + } + } + }; + + // test bug 638007 (new is optional), not using new + var wt = windowUtils.WindowTracker(delegate); + myWindow = makeEmptyWindow(); +}; + +// test that _unregWindow calls _unregLoadingWindow +exports['test window watcher unregs 4 loading wins'] = function(assert, done) { + var myWindow; + var finished = false; + let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"] + .getService(Ci.nsIWindowMediator) + .getMostRecentWindow("navigator:browser"); + var counter = 0; + + var delegate = { + onTrack: function(window) { + var type = window.document.documentElement.getAttribute("windowtype"); + if (type == "test:window") + assert.fail("onTrack shouldn't have been executed."); + } + }; + var wt = new windowUtils.WindowTracker(delegate); + + // make a new window + myWindow = makeEmptyWindow(); + + // make sure that the window hasn't loaded yet + assert.notEqual( + myWindow.document.readyState, + "complete", + "window hasn't loaded yet."); + + // unload WindowTracker + wt.unload(); + + // make sure that the window still hasn't loaded, which means that the onTrack + // would have been removed successfully assuming that it doesn't execute. + assert.notEqual( + myWindow.document.readyState, + "complete", + "window still hasn't loaded yet."); + + // wait for the window to load and then close it. onTrack wouldn't be called + // until the window loads, so we must let it load before closing it to be + // certain that onTrack was removed. + myWindow.addEventListener("load", function() { + // allow all of the load handles to execute before closing + myWindow.setTimeout(function() { + myWindow.addEventListener("unload", function() { + // once the window unloads test is done + done(); + }, false); + myWindow.close(); + }, 0); + }, false); +} + +exports['test window watcher without untracker'] = function(assert, done) { + var myWindow; + var finished = false; + + var delegate = { + onTrack: function(window) { + if (window == myWindow) { + assert.pass("onTrack() called with our test window"); + timer.setTimeout(function() { + myWindow.close(); + + if (!finished) { + finished = true; + myWindow = null; + wt.unload(); + done(); + } else { + assert.fail("onTrack() called multiple times."); + } + }, 1); + } + } + }; + + var wt = new windowUtils.WindowTracker(delegate); + myWindow = makeEmptyWindow(); +}; + +exports['test active window'] = function(assert, done) { + let testRunnerWindow = Cc["@mozilla.org/appshell/window-mediator;1"] + .getService(Ci.nsIWindowMediator) + .getMostRecentWindow("test:runner"); + let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"] + .getService(Ci.nsIWindowMediator) + .getMostRecentWindow("navigator:browser"); + + assert.equal(windowUtils.activeBrowserWindow, browserWindow, + "Browser window is the active browser window."); + + + let testSteps = [ + function() { + windowUtils.activeWindow = browserWindow; + continueAfterFocus(browserWindow); + }, + function() { + assert.equal(windowUtils.activeWindow, browserWindow, + "Correct active window [1]"); + continueAfterFocus(windowUtils.activeWindow = testRunnerWindow); + }, + function() { + assert.equal(windowUtils.activeWindow, testRunnerWindow, + "Correct active window [2]"); + assert.equal(windowUtils.activeBrowserWindow, browserWindow, + "Correct active browser window [3]"); + continueAfterFocus(windowUtils.activeWindow = browserWindow); + }, + function() { + assert.equal(windowUtils.activeWindow, browserWindow, + "Correct active window [4]"); + continueAfterFocus(windowUtils.activeWindow = testRunnerWindow); + }, + function() { + assert.equal(windowUtils.activeWindow, testRunnerWindow, + "Correct active window [5]"); + assert.equal(windowUtils.activeBrowserWindow, browserWindow, + "Correct active browser window [6]"); + testRunnerWindow = null; + browserWindow = null; + done(); + } + ]; + + let nextTest = function() { + let func = testSteps.shift(); + if (func) { + func(); + } + } + + function continueAfterFocus(targetWindow) { + + // Based on SimpleTest.waitForFocus + var fm = Cc["@mozilla.org/focus-manager;1"]. + getService(Ci.nsIFocusManager); + + var childTargetWindow = {}; + fm.getFocusedElementForWindow(targetWindow, true, childTargetWindow); + childTargetWindow = childTargetWindow.value; + + var focusedChildWindow = {}; + if (fm.activeWindow) { + fm.getFocusedElementForWindow(fm.activeWindow, true, focusedChildWindow); + focusedChildWindow = focusedChildWindow.value; + } + + var focused = (focusedChildWindow == childTargetWindow); + if (focused) { + nextTest(); + } else { + childTargetWindow.addEventListener("focus", function focusListener() { + childTargetWindow.removeEventListener("focus", focusListener, true); + nextTest(); + }, true); + } + + } + + nextTest(); +}; + +exports['test windowIterator'] = function(assert, done) { + // make a new window + let window = makeEmptyWindow(); + + // make sure that the window hasn't loaded yet + assert.notEqual( + window.document.readyState, + "complete", + "window hasn't loaded yet."); + + // this window should only appear in windowIterator() while its loading + assert.ok(toArray(windowUtils.windowIterator()).indexOf(window) === -1, + "window isn't in windowIterator()"); + + // Then it should be in windowIterator() + window.addEventListener("load", function onload() { + window.addEventListener("load", onload, false); + assert.ok(toArray(windowUtils.windowIterator()).indexOf(window) !== -1, + "window is now in windowIterator(false)"); + + // Wait for the window unload before ending test + window.addEventListener("unload", function onunload() { + window.addEventListener("unload", onunload, false); + done(); + }, false); + window.close(); + }, false); +} + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-window-utils2.js b/tools/addon-sdk-1.12/test/test-window-utils2.js new file mode 100644 index 0000000..a169746 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-window-utils2.js @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +const { Ci } = require('chrome'); +const { open, backgroundify, windows, + getXULWindow, getBaseWindow } = require('sdk/window/utils'); +const windowUtils = require('sdk/deprecated/window-utils'); + +exports['test get nsIBaseWindow from nsIDomWindow'] = function(assert) { + let active = windowUtils.activeBrowserWindow; + + assert.ok(!(active instanceof Ci.nsIBaseWindow), + 'active window is not nsIBaseWindow'); + + assert.ok(getBaseWindow(active) instanceof Ci.nsIBaseWindow, + 'base returns nsIBaseWindow'); +}; + +exports['test get nsIXULWindow from nsIDomWindow'] = function(assert) { + let active = windowUtils.activeBrowserWindow; + assert.ok(!(active instanceof Ci.nsIXULWindow), + 'active window is not nsIXULWindow'); + assert.ok(getXULWindow(active) instanceof Ci.nsIXULWindow, + 'base returns nsIXULWindow'); +}; + +exports['test top window creation'] = function(assert) { + let window = open('data:text/html;charset=utf-8,Hello top window'); + assert.ok(~windows().indexOf(window), 'window was opened'); + window.close(); +}; + +exports['test new top window with options'] = function(assert) { + let window = open('data:text/html;charset=utf-8,Hi custom top window', { + name: 'test', + features: { height: 100, width: 200, toolbar: true } + }); + assert.ok(~windows().indexOf(window), 'window was opened'); + assert.equal(window.name, 'test', 'name was set'); + assert.equal(window.innerHeight, 100, 'height is set'); + assert.equal(window.innerWidth, 200, 'height is set'); + assert.equal(window.toolbar.visible, true, 'toolbar was set'); + window.close(); +}; + +exports['test backgroundify'] = function(assert) { + let window = open('data:text/html;charset=utf-8,backgroundy'); + assert.ok(~windows().indexOf(window), + 'window is in the list of windows'); + let backgroundy = backgroundify(window); + assert.equal(backgroundy, window, 'backgroundify returs give window back'); + assert.ok(!~windows().indexOf(window), + 'backgroundifyied window is in the list of windows'); + window.close(); +}; + +require('test').run(exports); diff --git a/tools/addon-sdk-1.12/test/test-windows-common.js b/tools/addon-sdk-1.12/test/test-windows-common.js new file mode 100644 index 0000000..05cd8bf --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-windows-common.js @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { Loader } = require('sdk/test/loader'); +const { browserWindows } = require('sdk/windows'); + +// TEST: browserWindows Iterator +exports.testBrowserWindowsIterator = function(test) { + let activeWindowCount = 0; + let windows = []; + let i = 0; + for each (let window in browserWindows) { + if (window === browserWindows.activeWindow) + activeWindowCount++; + + test.assertEqual(windows.indexOf(window), -1, 'window not already in iterator'); + test.assertEqual(browserWindows[i++], window, 'browserWindows[x] works'); + windows.push(window); + } + test.assertEqual(activeWindowCount, 1, 'activeWindow was found in the iterator'); + + i = 0; + for (let j in browserWindows) { + test.assertEqual(j, i++, 'for (x in browserWindows) works'); + } +}; + +exports.testWindowTabsObject_alt = function(test) { + test.waitUntilDone(); + + let window = browserWindows.activeWindow; + window.tabs.open({ + url: "data:text/html;charset=utf-8,<title>tab 2</title>", + inBackground: true, + onReady: function onReady(tab) { + test.assertEqual(tab.title, "tab 2", "Correct new tab title"); + test.assertNotEqual(window.tabs.activeTab, tab, "Correct active tab"); + + // end test + tab.close(test.done()); + } + }); +}; + +// TEST: browserWindows.activeWindow +exports.testWindowActivateMethod_simple = function(test) { + let window = browserWindows.activeWindow; + let tab = window.tabs.activeTab; + + window.activate(); + + test.assertEqual(browserWindows.activeWindow, window, + "Active window is active after window.activate() call"); + test.assertEqual(window.tabs.activeTab, tab, + "Active tab is active after window.activate() call"); + +}; diff --git a/tools/addon-sdk-1.12/test/test-windows-private-browsing.js b/tools/addon-sdk-1.12/test/test-windows-private-browsing.js new file mode 100644 index 0000000..13bef93 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-windows-private-browsing.js @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { Cc, Ci } = require('chrome'); +const { browserWindows } = require('sdk/windows'); +const { pb, pbUtils } = require('private-browsing-helper'); + +const wm = Cc['@mozilla.org/appshell/window-mediator;1']. + getService(Ci.nsIWindowMediator); + +if (pbUtils.isWindowPBEnabled(wm.getMostRecentWindow('navigator:browser'))) { + exports.testPerWindowPrivateBrowsing_getter = function(test) { + let activeWindow = wm. + getMostRecentWindow('navigator:browser'); + + // is per-window PB implemented? + let currentState = activeWindow.gPrivateBrowsingUI.privateWindow; + + pbUtils.setMode(false, activeWindow); + + test.assertEqual(activeWindow.gPrivateBrowsingUI.privateWindow, + browserWindows.activeWindow.isPrivateBrowsing, + 'Active window is not in PB mode'); + + pbUtils.setMode(true, activeWindow); + + test.assertEqual(activeWindow.gPrivateBrowsingUI.privateWindow, + browserWindows.activeWindow.isPrivateBrowsing, + 'Active window is in PB mode'); + + pbUtils.setMode(currentState, activeWindow); + }; +}
\ No newline at end of file diff --git a/tools/addon-sdk-1.12/test/test-windows.js b/tools/addon-sdk-1.12/test/test-windows.js new file mode 100644 index 0000000..064a0f6 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-windows.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +if (require("sdk/system/xul-app").is("Firefox")) { + module.exports = require("./windows/test-firefox-windows"); +} +else if (require("sdk/system/xul-app").is("Fennec")) { + module.exports = require("./windows/test-fennec-windows"); +} diff --git a/tools/addon-sdk-1.12/test/test-xhr.js b/tools/addon-sdk-1.12/test/test-xhr.js new file mode 100644 index 0000000..6cf585b --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-xhr.js @@ -0,0 +1,82 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var xhr = require("sdk/net/xhr"); +var timer = require("sdk/timers"); +var { Loader } = require("sdk/test/loader"); +var xulApp = require("sdk/system/xul-app"); + +/* Test is intentionally disabled until platform bug 707256 is fixed. +exports.testAbortedXhr = function(test) { + var req = new xhr.XMLHttpRequest(); + test.assertEqual(xhr.getRequestCount(), 1); + req.abort(); + test.assertEqual(xhr.getRequestCount(), 0); +}; +*/ + +exports.testLocalXhr = function(test) { + var req = new xhr.XMLHttpRequest(); + req.overrideMimeType("text/plain"); + req.open("GET", module.uri); + req.onreadystatechange = function() { + if (req.readyState == 4 && (req.status == 0 || req.status == 200)) { + test.assertMatches(req.responseText, + /onreadystatechange/, + "XMLHttpRequest should get local files"); + timer.setTimeout( + function() { test.assertEqual(xhr.getRequestCount(), 0); + test.done(); }, + 0 + ); + } + }; + req.send(null); + test.assertEqual(xhr.getRequestCount(), 1); + test.waitUntilDone(4000); +}; + +exports.testUnload = function(test) { + var loader = Loader(module); + var sbxhr = loader.require("sdk/net/xhr"); + var req = new sbxhr.XMLHttpRequest(); + req.overrideMimeType("text/plain"); + req.open("GET", module.uri); + req.send(null); + test.assertEqual(sbxhr.getRequestCount(), 1); + loader.unload(); + test.assertEqual(sbxhr.getRequestCount(), 0); +}; + +exports.testResponseHeaders = function(test) { + var req = new xhr.XMLHttpRequest(); + req.overrideMimeType("text/plain"); + req.open("GET", module.uri); + req.onreadystatechange = function() { + if (req.readyState == 4 && (req.status == 0 || req.status == 200)) { + var headers = req.getAllResponseHeaders(); + if (xulApp.versionInRange(xulApp.platformVersion, "13.0a1", "*")) { + headers = headers.split("\r\n"); + if(headers.length == 1) { + headers = headers[0].split("\n"); + } + for(let i in headers) { + if(headers[i] && headers[i].search("Content-Type") >= 0) { + test.assertEqual(headers[i], "Content-Type: text/plain", + "XHR's headers are valid"); + } + } + } + else { + test.assert(headers === null || headers === "", + "XHR's headers are empty"); + } + test.done(); + } + }; + req.send(null); + test.assertEqual(xhr.getRequestCount(), 1); + test.waitUntilDone(4000); +} + diff --git a/tools/addon-sdk-1.12/test/test-xpcom.js b/tools/addon-sdk-1.12/test/test-xpcom.js new file mode 100644 index 0000000..17c7f1c --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-xpcom.js @@ -0,0 +1,223 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const xpcom = require("sdk/platform/xpcom"); +const { Cc, Ci, Cm, Cr } = require("chrome"); +const { isCIDRegistered } = Cm.QueryInterface(Ci.nsIComponentRegistrar); +const { Class } = require("sdk/core/heritage"); +const { Loader } = require("sdk/test/loader"); + +exports['test Unknown implements nsISupports'] = function(assert) { + let actual = xpcom.Unknown(); + assert.equal(actual.QueryInterface(Ci.nsISupports), + actual, + 'component implements nsISupports'); +}; + +exports['test implement xpcom interfaces'] = function(assert) { + let WeakReference = Class({ + extends: xpcom.Unknown, + interfaces: [ 'nsIWeakReference' ], + QueryReferent: function() {} + }); + let weakReference = WeakReference() + + assert.equal(weakReference.QueryInterface(Ci.nsISupports), + weakReference, + 'component implements nsISupports'); + assert.equal(weakReference.QueryInterface(Ci.nsIWeakReference), + weakReference, + 'component implements specified interface'); + + assert.throws(function() { + component.QueryInterface(Ci.nsIObserver); + }, "component does not implements interface"); + + let Observer = Class({ + extends: WeakReference, + interfaces: [ 'nsIObserver', 'nsIRequestObserver' ], + observe: function() {}, + onStartRequest: function() {}, + onStopRequest: function() {} + }); + let observer = Observer() + + assert.equal(observer.QueryInterface(Ci.nsISupports), + observer, + 'derived component implements nsISupports'); + assert.equal(observer.QueryInterface(Ci.nsIWeakReference), + observer, + 'derived component implements supers interface'); + assert.equal(observer.QueryInterface(Ci.nsIObserver), + observer.QueryInterface(Ci.nsIRequestObserver), + 'derived component implements specified interfaces'); +}; + +exports['test implement factory without contract'] = function(assert) { + let actual = xpcom.Factory({ + get wrappedJSObject() this, + }); + + assert.ok(isCIDRegistered(actual.id), 'factory is regiseterd'); + xpcom.unregister(actual); + assert.ok(!isCIDRegistered(actual.id), 'factory is unregistered'); +}; + +exports['test implement xpcom factory'] = function(assert) { + let Component = Class({ + extends: xpcom.Unknown, + interfaces: [ 'nsIObserver' ], + get wrappedJSObject() this, + observe: function() {} + }); + + let factory = xpcom.Factory({ + register: false, + contract: '@jetpack/test/factory;1', + Component: Component + }); + + assert.ok(!isCIDRegistered(factory.id), 'factory is not registered'); + xpcom.register(factory); + assert.ok(isCIDRegistered(factory.id), 'factory is registered'); + + let actual = Cc[factory.contract].createInstance(Ci.nsIObserver); + + assert.ok(actual.wrappedJSObject instanceof Component, + "createInstance returnes wrapped factory instances"); + + assert.notEqual(Cc[factory.contract].createInstance(Ci.nsIObserver), + Cc[factory.contract].createInstance(Ci.nsIObserver), + "createInstance returns new instance each time"); +}; + +exports['test implement xpcom service'] = function(assert) { + let actual = xpcom.Service({ + contract: '@jetpack/test/service;1', + register: false, + Component: Class({ + extends: xpcom.Unknown, + interfaces: [ 'nsIObserver'], + get wrappedJSObject() this, + observe: function() {}, + name: 'my-service' + }) + }); + + assert.ok(!isCIDRegistered(actual.id), 'component is not registered'); + xpcom.register(actual); + assert.ok(isCIDRegistered(actual.id), 'service is regiseterd'); + assert.ok(Cc[actual.contract].getService(Ci.nsIObserver).observe, + 'service can be accessed via get service'); + assert.equal(Cc[actual.contract].getService(Ci.nsIObserver).wrappedJSObject, + actual.component, + 'wrappedJSObject is an actual component'); + xpcom.unregister(actual); + assert.ok(!isCIDRegistered(actual.id), 'service is unregistered'); +}; + + +function testRegister(assert, text) { + + const service = xpcom.Service({ + description: 'test about:boop page', + contract: '@mozilla.org/network/protocol/about;1?what=boop', + register: false, + Component: Class({ + extends: xpcom.Unknown, + get wrappedJSObject() this, + interfaces: [ 'nsIAboutModule' ], + newChannel : function(aURI) { + var ios = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + + var channel = ios.newChannel( + "data:text/plain;charset=utf-8," + text, + null, + null + ); + + channel.originalURI = aURI; + return channel; + }, + getURIFlags: function(aURI) { + return Ci.nsIAboutModule.ALLOW_SCRIPT; + } + }) + }); + + xpcom.register(service); + + assert.equal(isCIDRegistered(service.id), true); + + var aboutFactory = xpcom.factoryByContract(service.contract); + var about = aboutFactory.createInstance(Ci.nsIAboutModule); + + var ios = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + assert.equal( + about.getURIFlags(ios.newURI("http://foo.com", null, null)), + Ci.nsIAboutModule.ALLOW_SCRIPT + ); + + var aboutURI = ios.newURI("about:boop", null, null); + var channel = ios.newChannelFromURI(aboutURI); + var iStream = channel.open(); + var siStream = Cc['@mozilla.org/scriptableinputstream;1'] + .createInstance(Ci.nsIScriptableInputStream); + siStream.init(iStream); + var data = new String(); + data += siStream.read(-1); + siStream.close(); + iStream.close(); + assert.equal(data, text); + + xpcom.unregister(service); + assert.equal(isCIDRegistered(service.id), false); +} + +exports["test register"] = function(assert) { + testRegister(assert, "hai2u"); +}; + +exports["test re-register"] = function(assert) { + testRegister(assert, "hai2u again"); +}; + +exports["test unload"] = function(assert) { + let loader = Loader(module); + let sbxpcom = loader.require("sdk/platform/xpcom"); + + let auto = sbxpcom.Factory({ + contract: "@mozilla.org/test/auto-unload;1", + description: "test auto", + name: "auto" + }); + + let manual = sbxpcom.Factory({ + contract: "@mozilla.org/test/manual-unload;1", + description: "test manual", + register: false, + unregister: false, + name: "manual" + }); + + assert.equal(isCIDRegistered(auto.id), true, 'component registered'); + assert.equal(isCIDRegistered(manual.id), false, 'component not registered'); + + sbxpcom.register(manual) + assert.equal(isCIDRegistered(manual.id), true, + 'component was automatically registered on first instance'); + loader.unload(); + + assert.equal(isCIDRegistered(auto.id), false, + 'component was atumatically unregistered on unload'); + assert.equal(isCIDRegistered(manual.id), true, + 'component was not automatically unregistered on unload'); + sbxpcom.unregister(manual); + assert.equal(isCIDRegistered(manual.id), false, + 'component was manually unregistered on unload'); +}; + +require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/test-xul-app.js b/tools/addon-sdk-1.12/test/test-xul-app.js new file mode 100644 index 0000000..c73f316 --- /dev/null +++ b/tools/addon-sdk-1.12/test/test-xul-app.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var xulApp = require("sdk/system/xul-app"); + +exports.testXulApp = function(test) { + test.assertEqual(typeof(xulApp.ID), "string", + "ID is a string"); + test.assertEqual(typeof(xulApp.name), "string", + "name is a string"); + test.assertEqual(typeof(xulApp.version), "string", + "version is a string"); + test.assertEqual(typeof(xulApp.platformVersion), "string", + "platformVersion is a string"); + + test.assertRaises(function() { xulApp.is("blargy"); }, + "Unkown Mozilla Application: blargy", + "is() throws error on bad app name"); + test.assertRaises(function() { xulApp.isOneOf(["blargy"]); }, + "Unkown Mozilla Application: blargy", + "isOneOf() throws error on bad app name"); + + function testSupport(name) { + var item = xulApp.is(name); + test.assert(item === true || item === false, + "is('" + name + "') is true or false."); + } + + var apps = ["Firefox", "Mozilla", "Sunbird", "SeaMonkey", + "Fennec", "Thunderbird"]; + + apps.forEach(function(name) { testSupport(name); }); + + test.assert(xulApp.isOneOf(apps) == true || + xulApp.isOneOf(apps) == false, + "isOneOf() returns true or false."); + + test.assertEqual(xulApp.versionInRange(xulApp.platformVersion, "1.9", "*"), + true, "platformVersion in range [1.9, *)"); + test.assertEqual(xulApp.versionInRange("3.6.4", "3.6.4", "3.6.*"), + true, "3.6.4 in [3.6.4, 3.6.*)"); + test.assertEqual(xulApp.versionInRange("1.9.3", "1.9.2", "1.9.3"), + false, "1.9.3 not in [1.9.2, 1.9.3)"); +}; diff --git a/tools/addon-sdk-1.12/test/traits/assert.js b/tools/addon-sdk-1.12/test/traits/assert.js new file mode 100644 index 0000000..5117ffb --- /dev/null +++ b/tools/addon-sdk-1.12/test/traits/assert.js @@ -0,0 +1,98 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var BaseAssert = require("sdk/test/assert").Assert; + +/** + * Whether or not given property descriptors are equivalent. They are + * equivalent either if both are marked as "conflict" or "required" property + * or if all the properties of descriptors are equal. + * @param {Object} actual + * @param {Object} expected + */ +function equivalentDescriptors(actual, expected) { + return (actual.conflict && expected.conflict) || + (actual.required && expected.required) || + equalDescriptors(actual, expected); +} + +function equalDescriptors(actual, expected) { + return actual.get === expected.get && + actual.set === expected.set && + actual.value === expected.value && + !!actual.enumerable === !!expected.enumerable && + !!actual.configurable === !!expected.configurable && + !!actual.writable === !!expected.writable; +} + +/** + * Whether or not given `target` array contains all the element + * from a given `source` array. + */ +function containsSet(source, target) { + return source.some(function(element) { + return 0 > target.indexOf(element); + }); +} + +/** + * Whether or not given two arrays contain all elements from another. + */ +function equivalentSets(source, target) { + return containsSet(source, target) && containsSet(target, source); +} + +/** + * Finds name of the property from `source` property descriptor map, that + * is not equivalent of the name named property in the `target` property + * descriptor map. If not found `null` is returned instead. + */ +function findNonEquivalentPropertyName(source, target) { + var value = null; + Object.getOwnPropertyNames(source).some(function(key) { + var areEquivalent = false; + if (!equivalentDescriptors(source[key], target[key])) { + value = key; + areEquivalent = true; + } + return areEquivalent; + }); + return value; +} + +var AssertDescriptor = { + equalTraits: { + value: function equivalentTraits(actual, expected, message) { + var difference; + var actualKeys = Object.getOwnPropertyNames(actual); + var expectedKeys = Object.getOwnPropertyNames(expected); + + if (equivalentSets(actualKeys, expectedKeys)) { + this.fail({ + operator: "equalTraits", + message: "Traits define different properties", + actual: actualKeys.sort().join(","), + expected: expectedKeys.sort().join(","), + }); + } + else if ((difference = findNonEquivalentPropertyName(actual, expected))) { + this.fail({ + operator: "equalTraits", + message: "Traits define non-equivalent property `" + difference + "`", + actual: actual[difference], + expected: expected[difference] + }); + } + else { + this.pass(message || "Traits are equivalent."); + } + } + } +}; + +exports.Assert = function Assert() { + return Object.create(BaseAssert.apply(null, arguments), AssertDescriptor); +}; diff --git a/tools/addon-sdk-1.12/test/traits/descriptor-tests.js b/tools/addon-sdk-1.12/test/traits/descriptor-tests.js new file mode 100644 index 0000000..e11bc46 --- /dev/null +++ b/tools/addon-sdk-1.12/test/traits/descriptor-tests.js @@ -0,0 +1,335 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var Trait = require("sdk/deprecated/light-traits").Trait; +var utils = require("./utils"); +var Data = utils.Data; +var Method = utils.Method; +var Accessor = utils.Accessor; +var Required = utils.Required; +var Conflict = utils.Conflict; + +function method() {} + +exports.Assert = require("./assert").Assert +exports["test simple composition"] = function(assert) { + var actual = Trait.compose( + Trait({ a: 0, b: 1 }), + { c: { value: 2 }, d: { value: method, enumerable: true } } + ); + + var expected = { + a: Data(0), + b: Data(1), + c: Data(2, false, false, false), + d: Method(method, true, false, false) + }; + + assert.equalTraits(actual, expected); +}; + +exports["test composition with conflict"] = function(assert) { + var actual = Trait.compose( + Trait({ a: 0, b: 1 }), + { + a: { + value: 2, + writable: true, + configurable: true, + enumerable: true + }, + c: { + value: method, + configurable: true + } + } + ); + + var expected = { + a: Conflict("a"), + b: Data(1), + c: Method(method, false, true, false) + }; + + assert.equalTraits(actual, expected); +}; + +exports["test identical props does not cause conflict"] = function(assert) { + var actual = Trait.compose( + { + a: { + value: 0, + writable: true, + configurable: true, + enumerable: true + }, + b: { + value: 1 + } + }, + Trait({ + a: 0, + c: method + }) + ); + + var expected = { + a: Data(0), + b: Data(1, false, false, false), + c: Method(method) + } + + assert.equalTraits(actual, expected); +}; + +exports["test composition with identical required props"] = function(assert) { + var actual = Trait.compose( + Trait({ a: Trait.required, b: 1 }), + { a: { required: true }, c: { value: method } } + ); + + var expected = { + a: Required(), + b: Data(1), + c: Method(method, false, false, false) + }; + + assert.equalTraits(actual, expected); +}; + +exports["test composition satisfying a required prop"] = function(assert) { + var actual = Trait.compose( + Trait({ a: Trait.required, b: 1 }), + { a: { value: method, enumerable: true } } + ); + + var expected = { + a: Method(method, true, false, false), + b: Data(1) + }; + + assert.equalTraits(actual, expected); +}; + +exports["test compose is neutral wrt conflicts"] = function(assert) { + var actual = Trait.compose( + Trait({ a: { value: 1 } }, Trait({ a: 2 })), + { b: { value: 0, writable: true, configurable: true, enumerable: false } } + ); + + var expected = { a: Conflict("a"), b: Data(0, false) }; + + assert.equalTraits(actual, expected); +}; + +exports["test conflicting prop overrides Trait.required"] = function(assert) { + var actual = Trait.compose( + Trait.compose( + Trait({ a: 1 }), + { a: { value: 2 } } + ), + { a: { value: Trait.required } } + ); + + var expected = { a: Conflict("a") }; + + assert.equalTraits(actual, expected); +}; + +exports["test compose is commutative"] = function(assert) { + var actual = Trait.compose( + Trait({ a: 0, b: 1 }), + { c: { value: 2 }, d: { value: method } } + ); + + var expected = Trait.compose( + { c: { value: 2 }, d: { value: method } }, + Trait({ a: 0, b: 1 }) + ); + + assert.equalTraits(actual, expected); +} + +exports["test compose is commutative, also for required/conflicting props"] = function(assert) { + var actual = Trait.compose( + { + a: { value: 0 }, + b: { value: 1 }, + c: { value: 3 }, + e: { value: Trait.required } + }, + { + c: { value: 2 }, + d: { get: method } + } + ); + + var expected = Trait.compose( + Trait({ c: 3 }), + { + c: { value: 2 }, + d: { get: method }, + a: { value: 0 }, + b: { value: 1 }, + e: { value: Trait.required }, + } + ); + + assert.equalTraits(actual, expected); +}; + +exports["test compose is associative"] = function(assert) { + var actual = Trait.compose( + { + a: { value: 0 }, + b: { value: 1 }, + c: { value: 3 }, + d: { value: Trait.required } + }, + Trait.compose( + { c: { value: 3 }, d: { value: Trait.required } }, + { c: { value: 2 }, d: { value: method }, e: { value: "foo" } } + ) + ); + + var expected = Trait.compose( + Trait.compose( + { + a: { value: 0 }, + b: { value: 1 }, + c: { value: 3 }, + d: { value: Trait.required } + }, + { + c: { value: 3 }, + d: { value: Trait.required } + } + ), + { + c: { value: 2 }, + d: { value: method }, + e: { value: "foo" } + } + ); + + assert.equalTraits(actual, expected); +}; + +exports["test diamond import of same prop do not conflict"] = function(assert) { + var actual = Trait.compose( + Trait.compose( + { b: { value: 2 } }, + { a: { value: 1, enumerable: true, configurable: true, writable: true } } + ), + Trait.compose( + { c: { value: 3 } }, + Trait({ a: 1 }) + ), + Trait({ d: 4 }) + ); + + var expected = { + a: Data(1), + b: Data(2, false, false, false), + c: Data(3, false, false, false), + d: Data(4) + }; + + assert.equalTraits(actual, expected); +}; + +exports["test create simple"] = function(assert) { + var o1 = Trait.compose( + Trait({ a: 1 }), + { + b: { + value: function() { + return this.a; + } + } + } + ).create(Object.prototype); + + assert.equal(Object.getPrototypeOf(o1), Object.prototype, "o1 prototype"); + assert.equal(1, o1.a, "o1.a"); + assert.equal(1, o1.b(), "o1.b()"); + assert.equal(Object.keys(o1).length, 1, "Object.keys(o1).length === 2"); +}; + +exports["test create with Array.prototype"] = function(assert) { + var o2 = Trait.compose({}, {}).create(Array.prototype); + assert.equal(Object.getPrototypeOf(o2), Array.prototype, "o2 prototype"); +}; + +exports["test exception for incomplete required properties"] = function(assert) { + assert.throws(function() { + Trait({ foo: Trait.required }).create(Object.prototype) + }, /Missing required property: `foo`/, "required prop error"); +} + +exports["test exception for unresolved conflicts"] = function(assert) { + assert.throws(function() { + Trait(Trait({ a: 0 }), Trait({ a: 1 })).create({}) + }, /Remaining conflicting property: `a`/, "conflicting prop error"); +} + +exports["test conflicting properties are present"] = function(assert) { + var o5 = Object.create(Object.prototype, Trait.compose( + { a: { value: 0 } }, + { a: { value: 1 } } + )); + + assert.ok("a" in o5, "conflicting property present"); + assert.throws(function() { + o5.a + }, /Remaining conflicting property: `a`/, "conflicting prop access error"); +}; + +exports["test diamond with conflicts"] = function(assert) { + function makeT1(x) { + return { + m: { + value: function() { + return x + } + } + }; + }; + + function makeT2(x) { + return Trait.compose( + Trait({ t2: "foo" }), + makeT1(x) + ); + }; + + function makeT3(x) { + return Trait.compose( + { + t3: { value: "bar" } + }, + makeT1(x) + ); + }; + + var T4 = Trait.compose(makeT2(5), makeT3(5)); + + assert.throws(function() { + T4.create(Object.prototype); + }, /Remaining conflicting property: `m`/, "diamond prop conflict"); +}; + +exports["test providing requirements through proto"] = function(assert) { + var t = Trait.compose( + {}, + { required: { required: true } } + ).create({ required: "test" }); + + assert.equal(t.required, "test", "property from proto is inherited"); +}; + +if (module == require.main) + require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/traits/inheritance-tests.js b/tools/addon-sdk-1.12/test/traits/inheritance-tests.js new file mode 100644 index 0000000..9c3f8a7 --- /dev/null +++ b/tools/addon-sdk-1.12/test/traits/inheritance-tests.js @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var Trait = require("sdk/deprecated/light-traits").Trait; + +exports["test custom constructor and inherited toString"] = function(assert) { + function Type() { + return Object.create(Type.prototype); + } + Type.prototype = Trait({ + method: function method() { + return 2; + } + }).create(Object.freeze(Type.prototype)); + + var fixture = Type(); + + assert.equal(fixture.constructor, Type, "must override constructor"); + assert.equal(fixture.toString(), "[object Type]", "must inherit toString"); +}; + +exports["test custom toString and inherited constructor"] = function(assert) { + function Type() { + return Object.create(Type.prototype); + } + Type.prototype = Trait({ + toString: function toString() { + return "<toString>"; + } + }).create(); + + var fixture = Type(); + + assert.equal(fixture.constructor, Trait, "must inherit constructor Trait"); + assert.equal(fixture.toString(), "<toString>", "Must override toString"); +}; + +exports["test custom toString and constructor"] = function(assert) { + function Type() { + return TypeTrait.create(Type.prototype); + } + Object.freeze(Type.prototype); + var TypeTrait = Trait({ + toString: function toString() { + return "<toString>"; + } + }); + + var fixture = Type(); + + assert.equal(fixture.constructor, Type, "constructor is provided to create"); + assert.equal(fixture.toString(), "<toString>", "toString was overridden"); +}; + +exports["test resolve constructor"] = function (assert) { + function Type() {} + var T1 = Trait({ constructor: Type }).resolve({ constructor: '_foo' }); + var f1 = T1.create(); + + assert.equal(f1._foo, Type, "constructor was resolved"); + assert.equal(f1.constructor, Trait, "constructor of prototype is inherited"); + assert.equal(f1.toString(), "[object Trait]", "toString is inherited"); +}; + +exports["test compose read-only"] = function (assert) { + function Type() {} + Type.prototype = Trait.compose(Trait({}), { + constructor: { value: Type }, + a: { value: "b", enumerable: true } + }).resolve({ a: "b" }).create({ a: "a" }); + + var f1 = new Type(); + + assert.equal(Object.getPrototypeOf(f1), Type.prototype, "inherits correctly"); + assert.equal(f1.constructor, Type, "constructor was overridden"); + assert.equal(f1.toString(), "[object Type]", "toString was inherited"); + assert.equal(f1.a, "a", "property a was resolved"); + assert.equal(f1.b, "b", "property a was renamed to b"); + assert.ok(!Object.getOwnPropertyDescriptor(Type.prototype, "a"), + "a is not on the prototype of the instance"); + + var proto = Object.getPrototypeOf(Type.prototype); + var dc = Object.getOwnPropertyDescriptor(Type.prototype, "constructor"); + var db = Object.getOwnPropertyDescriptor(Type.prototype, "b"); + var da = Object.getOwnPropertyDescriptor(proto, "a"); + + assert.ok(!dc.writable, "constructor is not writable"); + assert.ok(!dc.enumerable, "constructor is not enumerable"); + assert.ok(dc.configurable, "constructor inherits configurability"); + + assert.ok(!db.writable, "a -> b is not writable"); + assert.ok(db.enumerable, "a -> b is enumerable"); + assert.ok(!db.configurable, "a -> b is not configurable"); + + assert.ok(da.writable, "a is writable"); + assert.ok(da.enumerable, "a is enumerable"); + assert.ok(da.configurable, "a is configurable"); +}; + +if (require.main == module) + require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/traits/object-tests.js b/tools/addon-sdk-1.12/test/traits/object-tests.js new file mode 100644 index 0000000..ea53d2d --- /dev/null +++ b/tools/addon-sdk-1.12/test/traits/object-tests.js @@ -0,0 +1,321 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var Trait = require("sdk/deprecated/light-traits").Trait; +var utils = require("./utils"); +var Data = utils.Data; +var Method = utils.Method; +var Accessor = utils.Accessor; +var Required = utils.Required; +var Conflict = utils.Conflict; + +function method() {} + +exports.Assert = require("./assert").Assert; + +exports["test empty trait"] = function (assert) { + assert.equalTraits(Trait({}), {}); +}; + +exports["test simple trait"] = function (assert) { + var expected = { + a: Data(0, true, true, true), + b: Method(method, true, true, true) + }; + + assert.equalTraits(Trait({ a: 0, b: method }), expected); +}; + +exports["test simple trait with Trait.required property"] = function (assert) { + var actual = Trait({ a: Trait.required, b: 1 }); + var expected = { a: Required("a"), b: Data(1) }; + + assert.equalTraits(actual, expected); +}; + +exports["test ordering of trait properties is irrelevant"] = function (assert) { + var actual = Trait({ a: 0, b: 1, c: Trait.required }); + var expected = Trait({ b: 1, c: Trait.required, a: 0 }); + + assert.equalTraits(actual, expected); +}; + +exports["test trait with accessor property"] = function (assert) { + var record = { get a() {}, set a(v) {} }; + var get = Object.getOwnPropertyDescriptor(record, "a").get; + var set = Object.getOwnPropertyDescriptor(record, "a").set; + + assert.equalTraits(Trait(record), { a: Accessor(get, set) }); +}; + +exports["test simple composition"] = function (assert) { + var actual = Trait.compose(Trait({ a: 0, b: 1 }), Trait({ c: 2, d: method })); + var expected = { a: Data(0), b: Data(1), c: Data(2), d: Method(method) }; + + assert.equalTraits(actual, expected); +}; + +exports["test composition with conflict"] = function (assert) { + var actual = Trait.compose(Trait({ a: 0, b: 1 }), Trait({ a: 2, c: method })); + var expected = { a: Conflict("a"), b: Data(1), c: Method(method) }; + + assert.equalTraits(actual, expected); +}; + +exports["test composition of identical props does not cause conflict"] = function (assert) { + var actual = Trait.compose(Trait({ a: 0, b: 1 }), Trait({ a: 0, c: method })); + + assert.equalTraits(actual, { a: Data(0), b: Data(1), c: Method(method) }); +}; + +exports["test composition with identical Trait.required props"] = function (assert) { + var actual = Trait.compose(Trait({ a: Trait.required, b: 1 }), + Trait({ a: Trait.required, c: method })); + + assert.equalTraits(actual, { a: Required(), b: Data(1), c: Method(method) }); +}; + +exports["test composition satisfying a Trait.required prop"] = function (assert) { + var actual = Trait.compose(Trait({ a: Trait.required, b: 1 }), + Trait({ a: method })); + + assert.equalTraits(actual, { a: Method(method), b: Data(1) }); +}; + +exports["test compose is neutral wrt conflicts"] = function (assert) { + var actual = Trait.compose(Trait.compose(Trait({ a: 1 }), Trait({ a: 2 })), + Trait({ b: 0 })); + + assert.equalTraits(actual, { a: Conflict("a"), b: Data(0) }); +}; + +exports["test conflicting prop overrides Trait.required prop"] = function (assert) { + var actual = Trait.compose(Trait.compose(Trait({ a: 1 }), + Trait({ a: 2 })), + Trait({ a: Trait.required })); + + assert.equalTraits(actual, { a: Conflict("a") }); +}; + +exports["test compose is commutative"] = function (assert) { + var actual = Trait.compose(Trait({ a: 0, b: 1 }), Trait({ c: 2, d: method })); + var expected = Trait.compose(Trait({ c: 2, d: method }), + Trait({ a: 0, b: 1 })); + + assert.equalTraits(actual, expected); +}; + +exports["test compose is commutative, also for Trait.required/conflicting props"] = function (assert) { + var actual = Trait.compose(Trait({ a: 0, b: 1, c: 3, e: Trait.required }), + Trait({ c: 2, d: method })); + + var expected = Trait.compose(Trait({ c: 2, d: method }), + Trait({ a: 0, b: 1, c: 3, e: Trait.required })); + + assert.equalTraits(actual, expected); +}; + +exports["test compose is associative"] = function (assert) { + var actual = Trait.compose(Trait({ a: 0, b: 1, c: 3, d: Trait.required }), + Trait.compose(Trait({ c: 3, d: Trait.required }), + Trait({ c: 2, d: method, + e: "foo" }))); + + var expected = Trait.compose( + Trait.compose(Trait({ a: 0, b: 1, c: 3, d: Trait.required }), + Trait({ c: 3, d: Trait.required })), + Trait({ c: 2, d: method, e: "foo" })); + + assert.equalTraits(actual, expected); +}; + +exports["test diamond import of same prop does not generate conflict"] = function (assert) { + var actual = Trait.compose(Trait.compose(Trait({ b: 2 }), Trait({ a: 1 })), + Trait.compose(Trait({ c: 3 }), Trait({ a: 1 })), + Trait({ d: 4 })); + var expected = { a: Data(1), b: Data(2), c: Data(3), d: Data(4) }; + + assert.equalTraits(actual, expected); +}; + +exports["test resolve with empty resolutions has no effect"] = function (assert) { + assert.equalTraits(Trait({ a: 1, b: Trait.required, c: method }).resolve({}), + { a: Data(1), b: Required(), c: Method(method) }); +}; + +exports["test resolve: renaming"] = function (assert) { + var actual = Trait({ a: 1, b: Trait.required, c: method }); + + assert.equalTraits(actual.resolve({ a: "A", c: "C" }), + { A: Data(1), b: Required(), C: Method(method), + a: Required(), c: Required() }); +}; + +exports["test resolve: renaming to conflicting name causes conflict, order 1"] = function (assert) { + assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: "b" }), + { b: Conflict("b"), a: Required() }); +}; + +exports["test resolve: renaming to conflicting name causes conflict, order 2"] = function (assert) { + assert.equalTraits(Trait({ b: 2, a: 1 }).resolve({ a: "b" }), + { b: Conflict("b"), a: Required() }); +}; + +exports["test resolve: simple exclusion"] = function (assert) { + assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: undefined }), + { a: Required(), b: Data(2) }); +}; + +exports["test resolve: exclusion to empty trait"] = function (assert) { + assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: null, b: undefined }), + { a: Required(), b: Required() }); +}; + +exports["test resolve: exclusion and renaming of disjoint props"] = function (assert) { + assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: undefined, b: "c" }), + { a: Required(), c: Data(2), b: Required() }); +}; + +exports["test resolve: exclusion and renaming of overlapping props"] = function (assert) { + assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: undefined, b: "a" }), + { a: Data(2), b: Required() }); +}; + +exports["test resolve: renaming to a common alias causes conflict"] = function (assert) { + assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: "c", b: "c" }), + { c: Conflict("c"), a: Required(), b: Required() }); +}; + +exports["test resolve: renaming overrides Trait.required target"] = function (assert) { + assert.equalTraits(Trait({ a: Trait.required, b: 2 }).resolve({ b: "a" }), + { a: Data(2), b: Required() }); +}; + +exports["test resolve: renaming Trait.required properties has no effect"] = function (assert) { + assert.equalTraits(Trait({ a: 2, b: Trait.required }).resolve({ b: "a" }), + { a: Data(2), b: Required() }); +}; + +exports["test resolve: renaming of non-existent props has no effect"] = function (assert) { + assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: "c", d: "c" }), + { c: Data(1), b: Data(2), a: Required() }); +}; + +exports["test resolve: exclusion of non-existent props has no effect"] = function (assert) { + assert.equalTraits(Trait({ a: 1 }).resolve({ b: undefined }), { a: Data(1) }); +}; + +exports["test resolve is neutral w.r.t. Trait.required properties"] = function (assert) { + var actual = Trait({ a: Trait.required, b: Trait.required, c: "foo", d: 1 }); + var expected = { a: Required(), b: Required(), c: Data("foo"), d: Data(1) }; + assert.equalTraits(actual.resolve({ a: "c", b: undefined }), expected); +}; + +exports["test resolve supports swapping of property names, ordering 1"] = function (assert) { + assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ a: "b", b: "a" }), + { a: Data(2), b: Data(1) }); +}; + +exports["test resolve supports swapping of property names, ordering 2"] = function (assert) { + assert.equalTraits(Trait({ a: 1, b: 2 }).resolve({ b: "a", a: "b" }), + { a: Data(2), b: Data(1) }); +}; + +exports["test resolve supports swapping of property names, ordering 3"] = function (assert) { + assert.equalTraits(Trait({ b: 2, a: 1 }).resolve({ b: "a", a: "b" }), + { a: Data(2), b: Data(1) }); +}; + +exports["test resolve supports swapping of property names, ordering 4"] = function (assert) { + assert.equalTraits(Trait({ b: 2, a: 1 }).resolve({ a: "b", b: "a" }), + { a: Data(2), b: Data(1) }); +}; + +exports["test create simple"] = function (assert) { + var o1 = Trait({ + a: 1, + b: function () { + return this.a; + } + }).create(Object.prototype); + + assert.equal(Object.getPrototypeOf(o1), Object.prototype, "o1 prototype"); + assert.equal(1, o1.a, "o1.a"); + assert.equal(1, o1.b(), "o1.b()"); + assert.equal(Object.keys(o1).length, 2, "Object.keys(o1).length === 2"); +}; + +exports["test create with Array.prototype"] = function (assert) { + var o2 = Trait({}).create(Array.prototype); + assert.equal(Object.getPrototypeOf(o2), Array.prototype, "o2 prototype"); +}; + +exports["test exception for incomplete required properties"] = function (assert) { + assert.throws(function () { + Trait({ foo: Trait.required }).create(Object.prototype); + }, /Missing required property: `foo`/, "required prop error"); +}; + +exports["test exception for unresolved conflicts"] = function (assert) { + assert.throws(function () { + Trait.compose(Trait({ a: 0 }), Trait({ a: 1 })).create({}); + }, /Remaining conflicting property: `a`/, "conflicting prop error"); +}; + +exports["test verify that required properties are present but undefined"] = function (assert) { + var o4 = Object.create(Object.prototype, Trait({ foo: Trait.required })); + + assert.ok("foo" in o4, "required property present"); + assert.throws(function () { + o4.foo; + }, /Missing required property: `foo`/, "required prop error"); +}; + +exports["test verify that conflicting properties are present"] = function (assert) { + var o5 = Object.create(Object.prototype, Trait.compose(Trait({ a: 0 }), + Trait({ a: 1 }))); + + assert.ok("a" in o5, "conflicting property present"); + assert.throws(function () { + o5.a; + }, /Remaining conflicting property: `a`/, "conflicting prop access error"); +}; + +exports["test diamond with conflicts"] = function (assert) { + function makeT1(x) { + return Trait({ + m: function () { + return x + } + }) + }; + + function makeT2(x) { + return Trait.compose(Trait({ + t2: "foo" + }), makeT1(x)); + }; + + function makeT3(x) { + return Trait.compose(Trait({ + t3: "bar" + }), makeT1(x)); + }; + + var T4 = Trait.compose(makeT2(5), makeT3(5)); + + assert.throws(function () { + T4.create(Object.prototype); + }, /Remaining conflicting property: `m`/, "diamond prop conflict"); +}; + +exports["test providing requirements through proto"] = function (assert) { + var t = Trait({ required: Trait.required }).create({ required: "test" }); + assert.equal(t.required, "test", "property from proto is inherited"); +}; + +if (module == require.main) + require("test").run(exports); diff --git a/tools/addon-sdk-1.12/test/traits/utils.js b/tools/addon-sdk-1.12/test/traits/utils.js new file mode 100644 index 0000000..8426af7 --- /dev/null +++ b/tools/addon-sdk-1.12/test/traits/utils.js @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var ERR_CONFLICT = "Remaining conflicting property: "; +var ERR_REQUIRED = "Missing required property: "; + +exports.Data = function Data(value, enumerable, configurable, writable) { + return ({ + value: value, + enumerable: enumerable !== false, + configurable: configurable !== false, + writable: writable !== false + }); +}; + +exports.Method = function Method(method, enumerable, configurable, writable) { + return ({ + value: method, + enumerable: enumerable !== false, + configurable: configurable !== false, + writable: writable !== false + }); +}; + +exports.Accessor = function Accessor(get, set, enumerable, configurable) { + return ({ + get: get, + set: set, + enumerable: enumerable !== false, + configurable: configurable !== false + }); +}; + +exports.Required = function Required(name) { + function required() { throw new Error(ERR_REQUIRED + name) } + + return ({ + get: required, + set: required, + required: true + }); +}; + +exports.Conflict = function Conflict(name) { + function conflict() { throw new Error(ERR_CONFLICT + name) } + + return ({ + get: conflict, + set: conflict, + conflict: true + }); +}; + diff --git a/tools/addon-sdk-1.12/test/windows/test-fennec-windows.js b/tools/addon-sdk-1.12/test/windows/test-fennec-windows.js new file mode 100644 index 0000000..892d301 --- /dev/null +++ b/tools/addon-sdk-1.12/test/windows/test-fennec-windows.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { Cc, Ci } = require('chrome'); +const { setTimeout } = require('sdk/timers'); +const { Loader } = require('sdk/test/loader'); +const WM = Cc['@mozilla.org/appshell/window-mediator;1']. + getService(Ci.nsIWindowMediator); +const { browserWindows } = require('sdk/windows'); + +const ERR_MSG = 'This method is not yet supported by Fennec, consider using require("tabs") instead'; + +// TEST: browserWindows.length for Fennec +exports.testBrowserWindowsLength = function(test) { + test.assertEqual(browserWindows.length, 1, "Only one window open"); +}; + +// TEST: open & close window +exports.testOpenWindow = function(test) { + let tabCount = browserWindows.activeWindow.tabs.length; + let url = "data:text/html;charset=utf-8,<title>windows%20API%20test</title>"; + + try { + browserWindows.open({url: url}); + test.fail('Error was not thrown'); + } + catch(e) { + test.assertEqual(e.message, ERR_MSG, 'Error is thrown on windows.open'); + test.assertEqual(browserWindows.length, 1, "Only one window open"); + } +}; + +exports.testCloseWindow = function(test) { + let window = browserWindows.activeWindow; + + try { + window.close(); + test.fail('Error was not thrown'); + } + catch(e) { + test.assertEqual(e.message, ERR_MSG, 'Error is thrown on windows.close'); + test.assertEqual(browserWindows.length, 1, "Only one window open"); + } +}; diff --git a/tools/addon-sdk-1.12/test/windows/test-firefox-windows.js b/tools/addon-sdk-1.12/test/windows/test-firefox-windows.js new file mode 100644 index 0000000..a0cd4f9 --- /dev/null +++ b/tools/addon-sdk-1.12/test/windows/test-firefox-windows.js @@ -0,0 +1,369 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { Cc, Ci } = require('chrome'); +const { setTimeout } = require('sdk/timers'); +const { Loader } = require('sdk/test/loader'); +const wm = Cc['@mozilla.org/appshell/window-mediator;1']. + getService(Ci.nsIWindowMediator); +let browserWindows; + +function getTestRunnerWindow() wm.getMostRecentWindow("test:runner"); + +// TEST: open & close window +exports.testOpenAndCloseWindow = function(test) { + test.waitUntilDone(); + + test.assertEqual(browserWindows.length, 1, "Only one window open"); + + browserWindows.open({ + url: "data:text/html;charset=utf-8,<title>windows API test</title>", + onOpen: function(window) { + test.assertEqual(this, browserWindows, + "The 'this' object is the windows object."); + test.assertEqual(window.tabs.length, 1, "Only one tab open"); + test.assertEqual(browserWindows.length, 2, "Two windows open"); + window.tabs.activeTab.on('ready', function onReady(tab) { + tab.removeListener('ready', onReady); + test.assert(window.title.indexOf("windows API test") != -1, + "URL correctly loaded"); + window.close(); + }); + }, + onClose: function(window) { + test.assertEqual(window.tabs.length, 0, "Tabs were cleared"); + test.assertEqual(browserWindows.length, 1, "Only one window open"); + test.done(); + } + }); +}; + +exports.testAutomaticDestroy = function(test) { + test.waitUntilDone(); + + let windows = browserWindows; + + // Create a second windows instance that we will unload + let called = false; + let loader = Loader(module); + let windows2 = loader.require("sdk/windows").browserWindows; + windows2.on("open", function() { + called = true; + }); + + loader.unload(); + + // Fire a windows event and check that this unloaded instance is inactive + windows.open({ + url: "data:text/html;charset=utf-8,foo", + onOpen: function(window) { + setTimeout(function () { + test.assert(!called, + "Unloaded windows instance is destroyed and inactive"); + window.close(function () { + test.done(); + }); + }); + } + }); +}; + +exports.testWindowTabsObject = function(test) { + test.waitUntilDone(); + + browserWindows.open({ + url: "data:text/html;charset=utf-8,<title>tab 1</title>", + onOpen: function onOpen(window) { + test.assertEqual(window.tabs.length, 1, "Only 1 tab open"); + + window.tabs.open({ + url: "data:text/html;charset=utf-8,<title>tab 2</title>", + inBackground: true, + onReady: function onReady(newTab) { + test.assertEqual(window.tabs.length, 2, "New tab open"); + test.assertEqual(newTab.title, "tab 2", "Correct new tab title"); + test.assertEqual(window.tabs.activeTab.title, "tab 1", "Correct active tab"); + + let i = 1; + for each (let tab in window.tabs) + test.assertEqual(tab.title, "tab " + i++, "Correct title"); + + window.close(); + } + }); + }, + onClose: function onClose(window) { + test.assertEqual(window.tabs.length, 0, "No more tabs on closed window"); + test.done(); + } + }); +}; + +exports.testOnOpenOnCloseListeners = function(test) { + test.waitUntilDone(); + let windows = browserWindows; + + test.assertEqual(browserWindows.length, 1, "Only one window open"); + + let received = { + listener1: false, + listener2: false, + listener3: false, + listener4: false + } + + function listener1() { + test.assertEqual(this, windows, "The 'this' object is the windows object."); + if (received.listener1) + test.fail("Event received twice"); + received.listener1 = true; + } + + function listener2() { + if (received.listener2) + test.fail("Event received twice"); + received.listener2 = true; + } + + function listener3() { + test.assertEqual(this, windows, "The 'this' object is the windows object."); + if (received.listener3) + test.fail("Event received twice"); + received.listener3 = true; + } + + function listener4() { + if (received.listener4) + test.fail("Event received twice"); + received.listener4 = true; + } + + windows.on('open', listener1); + windows.on('open', listener2); + windows.on('close', listener3); + windows.on('close', listener4); + + function verify() { + test.assert(received.listener1, "onOpen handler called"); + test.assert(received.listener2, "onOpen handler called"); + test.assert(received.listener3, "onClose handler called"); + test.assert(received.listener4, "onClose handler called"); + + windows.removeListener('open', listener1); + windows.removeListener('open', listener2); + windows.removeListener('close', listener3); + windows.removeListener('close', listener4); + test.done(); + } + + + windows.open({ + url: "data:text/html;charset=utf-8,foo", + onOpen: function(window) { + window.close(verify); + } + }); +}; + +exports.testActiveWindow = function(test) { + const xulApp = require("sdk/system/xul-app"); + if (xulApp.versionInRange(xulApp.platformVersion, "1.9.2", "1.9.2.*")) { + test.pass("This test is disabled on 3.6. For more information, see bug 598525"); + return; + } + + let windows = browserWindows; + + // API window objects + let window2, window3; + + // Raw window objects + let nonBrowserWindow = getTestRunnerWindow(), rawWindow2, rawWindow3; + + test.waitUntilDone(); + + let testSteps = [ + function() { + test.assertEqual(windows.length, 3, "Correct number of browser windows"); + let count = 0; + for (let window in windows) + count++; + test.assertEqual(count, 3, "Correct number of windows returned by iterator"); + + rawWindow2.focus(); + continueAfterFocus(rawWindow2); + }, + function() { + nonBrowserWindow.focus(); + continueAfterFocus(nonBrowserWindow); + }, + function() { + /** + * Bug 614079: This test fails intermittently on some specific linux + * environnements, without being able to reproduce it in same + * distribution with same window manager. + * Disable it until being able to reproduce it easily. + + // On linux, focus is not consistent, so we can't be sure + // what window will be on top. + // Here when we focus "non-browser" window, + // Any Browser window may be selected as "active". + test.assert(windows.activeWindow == window2 || windows.activeWindow == window3, + "Non-browser windows aren't handled by this module"); + */ + window2.activate(); + continueAfterFocus(rawWindow2); + }, + function() { + test.assertEqual(windows.activeWindow.title, window2.title, "Correct active window - 2"); + window3.activate(); + continueAfterFocus(rawWindow3); + }, + function() { + test.assertEqual(windows.activeWindow.title, window3.title, "Correct active window - 3"); + nonBrowserWindow.focus(); + finishTest(); + } + ]; + + windows.open({ + url: "data:text/html;charset=utf-8,<title>window 2</title>", + onOpen: function(window) { + window2 = window; + rawWindow2 = wm.getMostRecentWindow("navigator:browser"); + + windows.open({ + url: "data:text/html;charset=utf-8,<title>window 3</title>", + onOpen: function(window) { + window.tabs.activeTab.on('ready', function onReady() { + window3 = window; + rawWindow3 = wm.getMostRecentWindow("navigator:browser"); + nextStep() + }); + } + }); + } + }); + + function nextStep() { + if (testSteps.length > 0) + testSteps.shift()(); + } + + function continueAfterFocus(targetWindow) { + + // Based on SimpleTest.waitForFocus + var fm = Cc["@mozilla.org/focus-manager;1"]. + getService(Ci.nsIFocusManager); + + var childTargetWindow = {}; + fm.getFocusedElementForWindow(targetWindow, true, childTargetWindow); + childTargetWindow = childTargetWindow.value; + + var focusedChildWindow = {}; + if (fm.activeWindow) { + fm.getFocusedElementForWindow(fm.activeWindow, true, focusedChildWindow); + focusedChildWindow = focusedChildWindow.value; + } + + var focused = (focusedChildWindow == childTargetWindow); + if (focused) { + nextStep(); + } else { + childTargetWindow.addEventListener("focus", function focusListener() { + childTargetWindow.removeEventListener("focus", focusListener, true); + nextStep(); + }, true); + } + + } + + function finishTest() { + window3.close(function() { + window2.close(function() { + test.done(); + }); + }); + } +}; + +exports.testTrackWindows = function(test) { + test.waitUntilDone(); + + let windows = []; + let actions = []; + + let expects = [ + "activate 0", "global activate 0", "deactivate 0", "global deactivate 0", + "activate 1", "global activate 1", "deactivate 1", "global deactivate 1", + "activate 2", "global activate 2" + ]; + + function shutdown(window) { + if (this.length === 1) { + test.assertEqual(actions.join(), expects.join(), + "correct activate and deactivate sequence") + + test.done(); + } + } + + function openWindow() { + windows.push(browserWindows.open({ + url: "data:text/html;charset=utf-8,<i>Hi</i>", + + onActivate: function(window) { + let index = windows.indexOf(window); + + actions.push("activate " + index); + + if (windows.length < 3) + openWindow() + else + for each (let win in windows) + win.close(shutdown) + }, + + onDeactivate: function(window) { + let index = windows.indexOf(window); + + actions.push("deactivate " + index) + } + })); + } + + browserWindows.on("activate", function (window) { + let index = windows.indexOf(window); + + actions.push("global activate " + index) + }) + + browserWindows.on("deactivate", function (window) { + let index = windows.indexOf(window); + + actions.push("global deactivate " + index) + }) + + openWindow(); +} + +// If the module doesn't support the app we're being run in, require() will +// throw. In that case, remove all tests above from exports, and add one dummy +// test that passes. +try { + browserWindows = require("sdk/windows").browserWindows; +} +catch (err) { + // This bug should be mentioned in the error message. + let bug = "https://bugzilla.mozilla.org/show_bug.cgi?id=571449"; + if (err.message.indexOf(bug) < 0) + throw err; + + module.exports = { + testAppNotSupported: function (test) { + test.pass("the windows module does not support this application."); + } + } +} |