diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /remote/test/unit | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'remote/test/unit')
-rw-r--r-- | remote/test/unit/test_Connection.js | 31 | ||||
-rw-r--r-- | remote/test/unit/test_DomainCache.js | 127 | ||||
-rw-r--r-- | remote/test/unit/test_Error.js | 119 | ||||
-rw-r--r-- | remote/test/unit/test_Format.js | 119 | ||||
-rw-r--r-- | remote/test/unit/test_Session.js | 42 | ||||
-rw-r--r-- | remote/test/unit/test_StreamRegistry.js | 172 | ||||
-rw-r--r-- | remote/test/unit/test_Sync.js | 230 | ||||
-rw-r--r-- | remote/test/unit/xpcshell.ini | 14 |
8 files changed, 854 insertions, 0 deletions
diff --git a/remote/test/unit/test_Connection.js b/remote/test/unit/test_Connection.js new file mode 100644 index 0000000000..093daea338 --- /dev/null +++ b/remote/test/unit/test_Connection.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { Connection } = ChromeUtils.import( + "chrome://remote/content/Connection.jsm" +); + +add_test(function test_Connection_splitMethod() { + for (const t of [42, null, true, {}, [], undefined]) { + Assert.throws( + () => Connection.splitMethod(t), + /TypeError/, + `${typeof t} throws` + ); + } + for (const s of ["", ".", "foo.", ".bar", "foo.bar.baz"]) { + Assert.throws( + () => Connection.splitMethod(s), + /Invalid method format: ".*"/, + `"${s}" throws` + ); + } + deepEqual(Connection.splitMethod("foo.bar"), { + domain: "foo", + command: "bar", + }); + + run_next_test(); +}); diff --git a/remote/test/unit/test_DomainCache.js b/remote/test/unit/test_DomainCache.js new file mode 100644 index 0000000000..e54ebbc314 --- /dev/null +++ b/remote/test/unit/test_DomainCache.js @@ -0,0 +1,127 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { Domain } = ChromeUtils.import( + "chrome://remote/content/domains/Domain.jsm" +); +const { DomainCache } = ChromeUtils.import( + "chrome://remote/content/domains/DomainCache.jsm" +); + +class MockSession { + onEvent() {} +} + +const noopSession = new MockSession(); + +add_test(function test_DomainCache_constructor() { + new DomainCache(noopSession, {}); + + run_next_test(); +}); + +add_test(function test_DomainCache_domainSupportsMethod() { + const modules = { + Foo: class extends Domain { + bar() {} + }, + }; + const domains = new DomainCache(noopSession, modules); + + ok(domains.domainSupportsMethod("Foo", "bar")); + ok(!domains.domainSupportsMethod("Foo", "baz")); + ok(!domains.domainSupportsMethod("foo", "bar")); + + run_next_test(); +}); + +add_test(function test_DomainCache_get_invalidModule() { + Assert.throws(() => { + const domains = new DomainCache(noopSession, { Foo: undefined }); + domains.get("Foo"); + }, /UnknownMethodError/); + + run_next_test(); +}); + +add_test(function test_DomainCache_get_missingConstructor() { + Assert.throws(() => { + const domains = new DomainCache(noopSession, { Foo: {} }); + domains.get("Foo"); + }, /TypeError/); + + run_next_test(); +}); + +add_test(function test_DomainCache_get_superClassNotDomain() { + Assert.throws(() => { + const domains = new DomainCache(noopSession, { Foo: class {} }); + domains.get("Foo"); + }, /TypeError/); + + run_next_test(); +}); + +add_test(function test_DomainCache_get_constructs() { + let eventFired; + class Session { + onEvent(event) { + eventFired = event; + } + } + + let constructed = false; + class Foo extends Domain { + constructor() { + super(); + constructed = true; + } + } + + const session = new Session(); + const domains = new DomainCache(session, { Foo }); + + const foo = domains.get("Foo"); + ok(constructed); + ok(foo instanceof Foo); + + const event = {}; + foo.emit(event); + equal(event, eventFired); + + run_next_test(); +}); + +add_test(function test_DomainCache_size() { + class Foo extends Domain {} + const domains = new DomainCache(noopSession, { Foo }); + + equal(domains.size, 0); + domains.get("Foo"); + equal(domains.size, 1); + + run_next_test(); +}); + +add_test(function test_DomainCache_clear() { + let dtorCalled = false; + class Foo extends Domain { + destructor() { + dtorCalled = true; + } + } + + const domains = new DomainCache(noopSession, { Foo }); + + equal(domains.size, 0); + domains.get("Foo"); + equal(domains.size, 1); + + domains.clear(); + equal(domains.size, 0); + ok(dtorCalled); + + run_next_test(); +}); diff --git a/remote/test/unit/test_Error.js b/remote/test/unit/test_Error.js new file mode 100644 index 0000000000..a4d81483d2 --- /dev/null +++ b/remote/test/unit/test_Error.js @@ -0,0 +1,119 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; +/* eslint-disable no-tabs */ + +const { + RemoteAgentError, + UnknownMethodError, + UnsupportedError, +} = ChromeUtils.import("chrome://remote/content/Error.jsm"); + +add_test(function test_RemoteAgentError_ctor() { + const e1 = new RemoteAgentError(); + equal(e1.name, "RemoteAgentError"); + equal(e1.message, ""); + equal(e1.cause, e1.message); + + const e2 = new RemoteAgentError("message"); + equal(e2.message, "message"); + equal(e2.cause, e2.message); + + const e3 = new RemoteAgentError("message", "cause"); + equal(e3.message, "message"); + equal(e3.cause, "cause"); + + run_next_test(); +}); + +add_test(function test_RemoteAgentError_notify() { + // nothing much we can test, except test that it doesn't throw + new RemoteAgentError().notify(); + + run_next_test(); +}); + +add_test(function test_RemoteAgentError_toString() { + const e = new RemoteAgentError("message"); + equal(e.toString(), RemoteAgentError.format(e)); + equal( + e.toString({ stack: true }), + RemoteAgentError.format(e, { stack: true }) + ); + + run_next_test(); +}); + +add_test(function test_RemoteAgentError_format() { + const { format } = RemoteAgentError; + + equal(format({ name: "HippoError" }), "HippoError"); + equal(format({ name: "HorseError", message: "neigh" }), "HorseError: neigh"); + + const dog = { + name: "DogError", + message: "woof", + stack: " one\ntwo\nthree ", + }; + equal(format(dog), "DogError: woof"); + equal( + format(dog, { stack: true }), + `DogError: woof: + one + two + three` + ); + + const cat = { + name: "CatError", + message: "meow", + stack: "four\nfive\nsix", + cause: dog, + }; + equal(format(cat), "CatError: meow"); + equal( + format(cat, { stack: true }), + `CatError: meow: + four + five + six +caused by: DogError: woof: + one + two + three` + ); + + run_next_test(); +}); + +add_test(function test_RemoteAgentError_fromJSON() { + const cdpErr = { + message: `TypeError: foo: + bar + baz`, + }; + const err = RemoteAgentError.fromJSON(cdpErr); + + equal(err.message, "TypeError: foo"); + equal(err.stack, "bar\nbaz"); + equal(err.cause, null); + + run_next_test(); +}); + +add_test(function test_UnsupportedError() { + ok(new UnsupportedError() instanceof RemoteAgentError); + run_next_test(); +}); + +add_test(function test_UnknownMethodError() { + ok(new UnknownMethodError() instanceof RemoteAgentError); + ok(new UnknownMethodError("domain").message.endsWith("domain")); + ok( + new UnknownMethodError("domain", "command").message.endsWith( + "domain.command" + ) + ); + run_next_test(); +}); diff --git a/remote/test/unit/test_Format.js b/remote/test/unit/test_Format.js new file mode 100644 index 0000000000..1787bdb491 --- /dev/null +++ b/remote/test/unit/test_Format.js @@ -0,0 +1,119 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { truncate, pprint } = ChromeUtils.import( + "chrome://remote/content/Format.jsm" +); + +const MAX_STRING_LENGTH = 250; +const HALF = "x".repeat(MAX_STRING_LENGTH / 2); + +add_test(function test_pprint() { + equal('[object Object] {"foo":"bar"}', pprint`${{ foo: "bar" }}`); + + equal("[object Number] 42", pprint`${42}`); + equal("[object Boolean] true", pprint`${true}`); + equal("[object Undefined] undefined", pprint`${undefined}`); + equal("[object Null] null", pprint`${null}`); + + let complexObj = { toJSON: () => "foo" }; + equal('[object Object] "foo"', pprint`${complexObj}`); + + let cyclic = {}; + cyclic.me = cyclic; + equal("[object Object] <cyclic object value>", pprint`${cyclic}`); + + let el = { + hasAttribute: attr => attr in el, + getAttribute: attr => (attr in el ? el[attr] : null), + nodeType: 1, + localName: "input", + id: "foo", + class: "a b", + href: "#", + name: "bar", + src: "s", + type: "t", + }; + equal( + '<input id="foo" class="a b" href="#" name="bar" src="s" type="t">', + pprint`${el}` + ); + + run_next_test(); +}); + +add_test(function test_truncate_empty() { + equal(truncate``, ""); + run_next_test(); +}); + +add_test(function test_truncate_noFields() { + equal(truncate`foo bar`, "foo bar"); + run_next_test(); +}); + +add_test(function test_truncate_multipleFields() { + equal(truncate`${0}`, "0"); + equal(truncate`${1}${2}${3}`, "123"); + equal(truncate`a${1}b${2}c${3}`, "a1b2c3"); + run_next_test(); +}); + +add_test(function test_truncate_primitiveFields() { + equal(truncate`${123}`, "123"); + equal(truncate`${true}`, "true"); + equal(truncate`${null}`, ""); + equal(truncate`${undefined}`, ""); + run_next_test(); +}); + +add_test(function test_truncate_string() { + equal(truncate`${"foo"}`, "foo"); + equal(truncate`${"x".repeat(250)}`, "x".repeat(250)); + equal(truncate`${"x".repeat(260)}`, `${HALF} ... ${HALF}`); + run_next_test(); +}); + +add_test(function test_truncate_array() { + equal(truncate`${["foo"]}`, JSON.stringify(["foo"])); + equal(truncate`${"foo"} ${["bar"]}`, `foo ${JSON.stringify(["bar"])}`); + equal( + truncate`${["x".repeat(260)]}`, + JSON.stringify([`${HALF} ... ${HALF}`]) + ); + + run_next_test(); +}); + +add_test(function test_truncate_object() { + equal(truncate`${{}}`, JSON.stringify({})); + equal(truncate`${{ foo: "bar" }}`, JSON.stringify({ foo: "bar" })); + equal( + truncate`${{ foo: "x".repeat(260) }}`, + JSON.stringify({ foo: `${HALF} ... ${HALF}` }) + ); + equal(truncate`${{ foo: ["bar"] }}`, JSON.stringify({ foo: ["bar"] })); + equal( + truncate`${{ foo: ["bar", { baz: 42 }] }}`, + JSON.stringify({ foo: ["bar", { baz: 42 }] }) + ); + + let complex = { + toString() { + return "hello world"; + }, + }; + equal(truncate`${complex}`, "hello world"); + + let longComplex = { + toString() { + return "x".repeat(260); + }, + }; + equal(truncate`${longComplex}`, `${HALF} ... ${HALF}`); + + run_next_test(); +}); diff --git a/remote/test/unit/test_Session.js b/remote/test/unit/test_Session.js new file mode 100644 index 0000000000..65d1b8bcf8 --- /dev/null +++ b/remote/test/unit/test_Session.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { Session } = ChromeUtils.import( + "chrome://remote/content/sessions/Session.jsm" +); + +const connection = { + registerSession: () => {}, + transport: { + on: () => {}, + }, +}; + +class MockTarget { + constructor() {} + + get browsingContext() { + return { id: 42 }; + } + + get mm() { + return { + addMessageListener() {}, + removeMessageListener() {}, + loadFrameScript() {}, + sendAsyncMessage() {}, + }; + } +} + +add_test(function test_Session_destructor() { + const session = new Session(connection, new MockTarget()); + session.domains.get("Browser"); + equal(session.domains.size, 1); + session.destructor(); + equal(session.domains.size, 0); + + run_next_test(); +}); diff --git a/remote/test/unit/test_StreamRegistry.js b/remote/test/unit/test_StreamRegistry.js new file mode 100644 index 0000000000..ed68577540 --- /dev/null +++ b/remote/test/unit/test_StreamRegistry.js @@ -0,0 +1,172 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AsyncShutdown } = ChromeUtils.import( + "resource://gre/modules/AsyncShutdown.jsm" +); +const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +const { StreamRegistry } = ChromeUtils.import( + "chrome://remote/content/StreamRegistry.jsm" +); + +add_test(function test_constructor() { + const registry = new StreamRegistry(); + equal(registry.streams.size, 0); + + run_next_test(); +}); + +add_test(async function test_destructor() { + const registry = new StreamRegistry(); + const { file: file1, path: path1 } = await createFile("foo bar"); + const { file: file2, path: path2 } = await createFile("foo bar"); + + registry.add(file1); + registry.add(file2); + + equal(registry.streams.size, 2); + + await registry.destructor(); + equal(registry.streams.size, 0); + ok(!(await OS.File.exists(path1)), "temporary file has been removed"); + ok(!(await OS.File.exists(path2)), "temporary file has been removed"); + + run_next_test(); +}); + +add_test(async function test_addValidStreamType() { + const registry = new StreamRegistry(); + const { file } = await createFile("foo bar"); + + const handle = registry.add(file); + equal(registry.streams.size, 1, "A single stream has been added"); + equal(typeof handle, "string", "Handle is of type string"); + ok(registry.streams.has(handle), "Handle has been found"); + equal(registry.streams.get(handle), file, "Expected OS.File stream found"); + + run_next_test(); +}); + +add_test(async function test_addCreatesDifferentHandles() { + const registry = new StreamRegistry(); + const { file } = await createFile("foo bar"); + + const handle1 = registry.add(file); + equal(registry.streams.size, 1, "A single stream has been added"); + equal(typeof handle1, "string", "Handle is of type string"); + ok(registry.streams.has(handle1), "Handle has been found"); + equal(registry.streams.get(handle1), file, "Expected OS.File stream found"); + + const handle2 = registry.add(file); + equal(registry.streams.size, 2, "A single stream has been added"); + equal(typeof handle2, "string", "Handle is of type string"); + ok(registry.streams.has(handle2), "Handle has been found"); + equal(registry.streams.get(handle2), file, "Expected OS.File stream found"); + + notEqual(handle1, handle2, "Different handles have been generated"); + + run_next_test(); +}); + +add_test(async function test_addInvalidStreamType() { + const registry = new StreamRegistry(); + Assert.throws(() => registry.add(new Blob([])), /UnsupportedError/); + + run_next_test(); +}); + +add_test(async function test_getForValidHandle() { + const registry = new StreamRegistry(); + const { file } = await createFile("foo bar"); + const handle = registry.add(file); + + equal(registry.streams.size, 1, "A single stream has been added"); + equal(registry.get(handle), file, "Expected OS.File stream found"); + + run_next_test(); +}); + +add_test(async function test_getForInvalidHandle() { + const registry = new StreamRegistry(); + const { file } = await createFile("foo bar"); + registry.add(file); + + equal(registry.streams.size, 1, "A single stream has been added"); + Assert.throws(() => registry.get("foo"), /TypeError/); + + run_next_test(); +}); + +add_test(async function test_removeForValidHandle() { + const registry = new StreamRegistry(); + const { file: file1, path: path1 } = await createFile("foo bar"); + const { file: file2, path: path2 } = await createFile("foo bar"); + + const handle1 = registry.add(file1); + const handle2 = registry.add(file2); + + equal(registry.streams.size, 2); + + await registry.remove(handle1); + equal(registry.streams.size, 1); + ok( + !(await OS.File.exists(path1)), + "temporary file for first stream has been removed" + ); + equal(registry.get(handle2), file2, "Second stream has not been closed"); + ok( + await OS.File.exists(path2), + "temporary file for second stream hasn't been removed" + ); + + run_next_test(); +}); + +add_test(async function test_removeForInvalidHandle() { + const registry = new StreamRegistry(); + const { file } = await createFile("foo bar"); + registry.add(file); + + equal(registry.streams.size, 1, "A single stream has been added"); + await Assert.rejects(registry.remove("foo"), /TypeError/); + + run_next_test(); +}); + +async function createFile(contents, options = {}) { + let { path = null, remove = true } = options; + + if (!path) { + const basePath = OS.Path.join(OS.Constants.Path.tmpDir, "remote-agent.txt"); + const { file, path: tmpPath } = await OS.File.openUnique(basePath, { + humanReadable: true, + }); + await file.close(); + path = tmpPath; + } + + let encoder = new TextEncoder(); + let array = encoder.encode(contents); + + const count = await OS.File.writeAtomic(path, array, { + encoding: "utf-8", + tmpPath: path + ".tmp", + }); + equal(count, contents.length, "All data has been written to file"); + + const file = await OS.File.open(path); + + // Automatically remove the file once the test has finished + if (remove) { + registerCleanupFunction(async () => { + await file.close(); + await OS.File.remove(path, { ignoreAbsent: true }); + }); + } + + return { file, path }; +} diff --git a/remote/test/unit/test_Sync.js b/remote/test/unit/test_Sync.js new file mode 100644 index 0000000000..36150c4cc3 --- /dev/null +++ b/remote/test/unit/test_Sync.js @@ -0,0 +1,230 @@ +/* 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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +const { PollPromise } = ChromeUtils.import("chrome://remote/content/Sync.jsm"); + +/** + * Mimic a DOM node for listening for events. + */ +class MockElement { + constructor() { + this.capture = false; + this.func = null; + this.eventName = null; + this.untrusted = false; + } + + addEventListener(name, func, capture, untrusted) { + this.eventName = name; + this.func = func; + if (capture != null) { + this.capture = capture; + } + if (untrusted != null) { + this.untrusted = untrusted; + } + } + + click() { + if (this.func) { + let details = { + capture: this.capture, + target: this, + type: this.eventName, + untrusted: this.untrusted, + }; + this.func(details); + } + } + + removeEventListener(name, func) { + this.capture = false; + this.func = null; + this.eventName = null; + this.untrusted = false; + } +} + +/** + * Mimic a message manager for sending messages. + */ +class MessageManager { + constructor() { + this.func = null; + this.message = null; + } + + addMessageListener(message, func) { + this.func = func; + this.message = message; + } + + removeMessageListener(message) { + this.func = null; + this.message = null; + } + + send(message, data) { + if (this.func) { + this.func({ + data, + message, + target: this, + }); + } + } +} + +/** + * Mimics nsITimer, but instead of using a system clock you can + * preprogram it to invoke the callback after a given number of ticks. + */ +class MockTimer { + constructor(ticksBeforeFiring) { + this.goal = ticksBeforeFiring; + this.ticks = 0; + this.cancelled = false; + } + + initWithCallback(cb, timeout, type) { + this.ticks++; + if (this.ticks >= this.goal) { + cb(); + } + } + + cancel() { + this.cancelled = true; + } +} + +add_test(function test_executeSoon_callback() { + // executeSoon() is already defined for xpcshell in head.js. As such import + // our implementation into a custom namespace. + let sync = {}; + ChromeUtils.import("chrome://remote/content/Sync.jsm", sync); + + for (let func of ["foo", null, true, [], {}]) { + Assert.throws(() => sync.executeSoon(func), /TypeError/); + } + + let a; + sync.executeSoon(() => { + a = 1; + }); + executeSoon(() => equal(1, a)); + + run_next_test(); +}); + +add_test(function test_PollPromise_funcTypes() { + for (let type of ["foo", 42, null, undefined, true, [], {}]) { + Assert.throws(() => new PollPromise(type), /TypeError/); + } + new PollPromise(() => {}); + new PollPromise(function() {}); + + run_next_test(); +}); + +add_test(function test_PollPromise_timeoutTypes() { + for (let timeout of ["foo", true, [], {}]) { + Assert.throws(() => new PollPromise(() => {}, { timeout }), /TypeError/); + } + for (let timeout of [1.2, -1]) { + Assert.throws(() => new PollPromise(() => {}, { timeout }), /RangeError/); + } + for (let timeout of [null, undefined, 42]) { + new PollPromise(resolve => resolve(1), { timeout }); + } + + run_next_test(); +}); + +add_test(function test_PollPromise_intervalTypes() { + for (let interval of ["foo", null, true, [], {}]) { + Assert.throws(() => new PollPromise(() => {}, { interval }), /TypeError/); + } + for (let interval of [1.2, -1]) { + Assert.throws(() => new PollPromise(() => {}, { interval }), /RangeError/); + } + new PollPromise(() => {}, { interval: 42 }); + + run_next_test(); +}); + +add_task(async function test_PollPromise_retvalTypes() { + for (let typ of [true, false, "foo", 42, [], {}]) { + strictEqual(typ, await new PollPromise(resolve => resolve(typ))); + } +}); + +add_task(async function test_PollPromise_rethrowError() { + let nevals = 0; + let err; + try { + await PollPromise(() => { + ++nevals; + throw new Error(); + }); + } catch (e) { + err = e; + } + equal(1, nevals); + ok(err instanceof Error); +}); + +add_task(async function test_PollPromise_noTimeout() { + let nevals = 0; + await new PollPromise((resolve, reject) => { + ++nevals; + nevals < 100 ? reject() : resolve(); + }); + equal(100, nevals); +}); + +add_task(async function test_PollPromise_zeroTimeout() { + // run at least once when timeout is 0 + let nevals = 0; + let start = new Date().getTime(); + await new PollPromise( + (resolve, reject) => { + ++nevals; + reject(); + }, + { timeout: 0 } + ); + let end = new Date().getTime(); + equal(1, nevals); + less(end - start, 500); +}); + +add_task(async function test_PollPromise_timeoutElapse() { + let nevals = 0; + let start = new Date().getTime(); + await new PollPromise( + (resolve, reject) => { + ++nevals; + reject(); + }, + { timeout: 100 } + ); + let end = new Date().getTime(); + lessOrEqual(nevals, 11); + greaterOrEqual(end - start, 100); +}); + +add_task(async function test_PollPromise_interval() { + let nevals = 0; + await new PollPromise( + (resolve, reject) => { + ++nevals; + reject(); + }, + { timeout: 100, interval: 100 } + ); + equal(2, nevals); +}); diff --git a/remote/test/unit/xpcshell.ini b/remote/test/unit/xpcshell.ini new file mode 100644 index 0000000000..8e004e8dd2 --- /dev/null +++ b/remote/test/unit/xpcshell.ini @@ -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/. + +[DEFAULT] +skip-if = appname == "thunderbird" + +[test_Connection.js] +[test_DomainCache.js] +[test_Error.js] +[test_Format.js] +[test_Session.js] +[test_StreamRegistry.js] +[test_Sync.js] |