diff options
Diffstat (limited to 'dom/promise/tests')
22 files changed, 2473 insertions, 0 deletions
diff --git a/dom/promise/tests/chrome.ini b/dom/promise/tests/chrome.ini new file mode 100644 index 000000000..c6dc855c4 --- /dev/null +++ b/dom/promise/tests/chrome.ini @@ -0,0 +1,7 @@ +[DEFAULT] + +[test_on_new_promise.html] +[test_on_promise_settled.html] +[test_on_promise_settled_duplicates.html] +[test_promise_xrays.html] +support-files = file_promise_xrays.html diff --git a/dom/promise/tests/file_promise_and_timeout_ordering.js b/dom/promise/tests/file_promise_and_timeout_ordering.js new file mode 100644 index 000000000..c83b5a6ac --- /dev/null +++ b/dom/promise/tests/file_promise_and_timeout_ordering.js @@ -0,0 +1,18 @@ +var log = []; +var resolvedPromise = Promise.resolve(null); +function schedulePromiseTask(f) { + resolvedPromise.then(f); +} + +setTimeout(function() { + log.push('t1start'); + schedulePromiseTask(function() { + log.push('promise'); + }); + log.push('t1end'); +}, 10); + +setTimeout(function() { + log.push('t2'); + postMessage(log.join(', ')); +}, 10); diff --git a/dom/promise/tests/file_promise_xrays.html b/dom/promise/tests/file_promise_xrays.html new file mode 100644 index 000000000..73f9bf7d7 --- /dev/null +++ b/dom/promise/tests/file_promise_xrays.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> + <script> + function vendGetter(name) { + return function() { throw "Getting " + String(name) }; + } + function vendSetter(name) { + return function() { throw "Setting " + String(name) }; + } + var setupThrew = false; + try { + // Neuter everything we can think of on Promise. + for (var obj of [Promise, Promise.prototype]) { + propNames = Object.getOwnPropertyNames(obj); + propNames = propNames.concat(Object.getOwnPropertySymbols(obj)); + for (var propName of propNames) { + if ((propName == "prototype" || + propName == Symbol.hasInstance) && + obj == Promise) { + // They're not configurable. + continue; + } + Object.defineProperty(obj, propName, + { get: vendGetter(propName), set: vendSetter(propName) }); + } + } + } catch (e) { + // Something went wrong. Save that info so the test can check for it. + setupThrew = e; + } + </script> +</html> diff --git a/dom/promise/tests/mochitest.ini b/dom/promise/tests/mochitest.ini new file mode 100644 index 000000000..0e6466d16 --- /dev/null +++ b/dom/promise/tests/mochitest.ini @@ -0,0 +1,18 @@ +[DEFAULT] +support-files = + promise_uncatchable_exception.js + +[test_bug883683.html] +[test_promise.html] +[test_promise_uncatchable_exception.html] +skip-if = debug == false +[test_promise_utils.html] +[test_resolve.html] +[test_resolver_return_value.html] +[test_thenable_vs_promise_ordering.html] +[test_promise_and_timeout_ordering.html] +support-files = file_promise_and_timeout_ordering.js +[test_promise_and_timeout_ordering_workers.html] +support-files = file_promise_and_timeout_ordering.js +[test_species_getter.html] +[test_webassembly_compile.html] diff --git a/dom/promise/tests/promise_uncatchable_exception.js b/dom/promise/tests/promise_uncatchable_exception.js new file mode 100644 index 000000000..d062e21af --- /dev/null +++ b/dom/promise/tests/promise_uncatchable_exception.js @@ -0,0 +1,9 @@ +postMessage("Done", "*"); + +var p = new Promise(function(resolve, reject) { + TestFunctions.throwUncatchableException(); + ok(false, "Shouldn't get here!"); +}).catch(function(exception) { + ok(false, "Shouldn't get here!"); +}); +ok(false, "Shouldn't get here!"); diff --git a/dom/promise/tests/test_bug883683.html b/dom/promise/tests/test_bug883683.html new file mode 100644 index 000000000..ab23ca10d --- /dev/null +++ b/dom/promise/tests/test_bug883683.html @@ -0,0 +1,41 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>Promise - bug 883683</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"><!-- + +function runTest() { + [{}, {}, {}, {}, {}].reduce(Promise.reject.bind(Promise)); + ok(true, "No leaks with reject?"); + + [{}, {}, {}, {}, {}].reduce(Promise.resolve.bind(Promise)); + ok(true, "No leaks with resolve?"); + + [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { throw a; }); }); + ok(true, "No leaks with exception?"); + + [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { }); }); + ok(true, "No leaks with empty promise?"); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +runTest(); +// --> +</script> +</pre> +</body> +</html> diff --git a/dom/promise/tests/test_on_new_promise.html b/dom/promise/tests/test_on_new_promise.html new file mode 100644 index 000000000..634dd7dda --- /dev/null +++ b/dom/promise/tests/test_on_new_promise.html @@ -0,0 +1,45 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!-- +Bug 1083210 - Sanity test for interaction between DOM promises and +Debugger.prototype.onNewPromise. +--> + +<html> +<head> + <title>Test for interaction with SpiderMonkey's Debugger.prototype.onNewPromise</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> + <script type="application/javascript"> + is(Object.prototype.toString.call(new Promise(function () {})), + "[object Promise]", + "We should have the native DOM promise implementation."); + + var Cu = Components.utils; + Cu.import("resource://gre/modules/jsdebugger.jsm"); + var dbgGlobal = new Cu.Sandbox(document.nodePrincipal); + addDebuggerToGlobal(dbgGlobal); + var dbg = new dbgGlobal.Debugger(this); + + var wrappedPromise; + dbg.onNewPromise = function (wp) { wrappedPromise = wp; }; + + var promise = new Promise(function () {}); + debugger; + ok(wrappedPromise); + is(wrappedPromise.unsafeDereference(), promise); + </script> +</pre> +</body> +</html> + diff --git a/dom/promise/tests/test_on_promise_settled.html b/dom/promise/tests/test_on_promise_settled.html new file mode 100644 index 000000000..4061d3997 --- /dev/null +++ b/dom/promise/tests/test_on_promise_settled.html @@ -0,0 +1,54 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!-- +Bug 1084065 - Sanity test for interaction between DOM promises and +Debugger.prototype.onPromiseResolved. +--> + +<html> +<head> + <title>Test for interaction with SpiderMonkey's Debugger.prototype.onNewPromise</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + is(Object.prototype.toString.call(new Promise(function () {})), + "[object Promise]", + "We should have the native DOM promise implementation."); + + var Cu = Components.utils; + Cu.import("resource://gre/modules/jsdebugger.jsm"); + var dbgGlobal = new Cu.Sandbox(document.nodePrincipal); + addDebuggerToGlobal(dbgGlobal); + var dbg = new dbgGlobal.Debugger(this); + + var wrappedPromise; + dbg.onPromiseSettled = function (wp) { wrappedPromise = wp; }; + + var promise = Promise.resolve(); + promise + .then(function () { + ok(wrappedPromise); + is(wrappedPromise.unsafeDereference(), promise); + dbg.onPromiseSettled = undefined; + }) + .then(null, function (e) { + ok(false, "Got an unexpected error: " + e); + }) + .then(SimpleTest.finish); + </script> +</pre> +</body> +</html> + diff --git a/dom/promise/tests/test_on_promise_settled_duplicates.html b/dom/promise/tests/test_on_promise_settled_duplicates.html new file mode 100644 index 000000000..5a1eddb1e --- /dev/null +++ b/dom/promise/tests/test_on_promise_settled_duplicates.html @@ -0,0 +1,59 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!-- +Bug 1084065 - Test that Debugger.prototype.onPromiseResolved doesn't get dupes. +--> + +<html> +<head> + <title>Test for interaction with SpiderMonkey's Debugger.prototype.onNewPromise</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + is(Object.prototype.toString.call(new Promise(function () {})), + "[object Promise]", + "We should have the native DOM promise implementation."); + + var Cu = Components.utils; + Cu.import("resource://gre/modules/jsdebugger.jsm"); + var dbgGlobal = new Cu.Sandbox(document.nodePrincipal); + addDebuggerToGlobal(dbgGlobal); + var dbg = new dbgGlobal.Debugger(this); + + var seen = new Set(); + dbg.onPromiseSettled = function (wp) { + is(seen.has(wp), false); + seen.add(wp); + }; + + var promise = new Promise(function (fulfill, reject) { + fulfill(1); + fulfill(2); + fulfill(3); + }); + + promise + .then(function () { + dbg.onPromiseSettled = undefined; + }) + .then(null, function (e) { + ok(false, "Got an unexpected error: " + e); + }) + .then(SimpleTest.finish); + </script> +</pre> +</body> +</html> + diff --git a/dom/promise/tests/test_promise.html b/dom/promise/tests/test_promise.html new file mode 100644 index 000000000..af185efcd --- /dev/null +++ b/dom/promise/tests/test_promise.html @@ -0,0 +1,831 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>Basic Promise Test</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"><!-- + +function promiseResolve() { + ok(Promise, "Promise object should exist"); + + var promise = new Promise(function(resolve, reject) { + ok(resolve, "Promise.resolve exists"); + ok(reject, "Promise.reject exists"); + + resolve(42); + }).then(function(what) { + ok(true, "Then - resolveCb has been called"); + is(what, 42, "ResolveCb received 42"); + runTest(); + }, function() { + ok(false, "Then - rejectCb has been called"); + runTest(); + }); +} + +function promiseResolveNoArg() { + var promise = new Promise(function(resolve, reject) { + ok(resolve, "Promise.resolve exists"); + ok(reject, "Promise.reject exists"); + + resolve(); + }).then(function(what) { + ok(true, "Then - resolveCb has been called"); + is(what, undefined, "ResolveCb received undefined"); + runTest(); + }, function() { + ok(false, "Then - rejectCb has been called"); + runTest(); + }); +} + +function promiseReject() { + var promise = new Promise(function(resolve, reject) { + reject(42); + }).then(function(what) { + ok(false, "Then - resolveCb has been called"); + runTest(); + }, function(what) { + ok(true, "Then - rejectCb has been called"); + is(what, 42, "RejectCb received 42"); + runTest(); + }); +} + +function promiseRejectNoHandler() { + // This test only checks that the code that reports unhandled errors in the + // Promises implementation does not crash or leak. + var promise = new Promise(function(res, rej) { + noSuchMethod(); + }); + runTest(); +} + +function promiseRejectNoArg() { + var promise = new Promise(function(resolve, reject) { + reject(); + }).then(function(what) { + ok(false, "Then - resolveCb has been called"); + runTest(); + }, function(what) { + ok(true, "Then - rejectCb has been called"); + is(what, undefined, "RejectCb received undefined"); + runTest(); + }); +} + +function promiseException() { + var promise = new Promise(function(resolve, reject) { + throw 42; + }).then(function(what) { + ok(false, "Then - resolveCb has been called"); + runTest(); + }, function(what) { + ok(true, "Then - rejectCb has been called"); + is(what, 42, "RejectCb received 42"); + runTest(); + }); +} + +function promiseGC() { + var resolve; + var promise = new Promise(function(r1, r2) { + resolve = r1; + }).then(function(what) { + ok(true, "Then - promise is still alive"); + runTest(); + }); + + promise = null; + + SpecialPowers.gc(); + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + + resolve(42); +} + +function promiseAsync_TimeoutResolveThen() { + var handlerExecuted = false; + + setTimeout(function() { + ok(handlerExecuted, "Handler should have been called before the timeout."); + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }, 0); + + Promise.resolve().then(function() { + handlerExecuted = true; + }); + + ok(!handlerExecuted, "Handlers are not called before 'then' returns."); +} + +function promiseAsync_ResolveTimeoutThen() { + var handlerExecuted = false; + + var promise = Promise.resolve(); + + setTimeout(function() { + ok(handlerExecuted, "Handler should have been called before the timeout."); + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }, 0); + + promise.then(function() { + handlerExecuted = true; + }); + + ok(!handlerExecuted, "Handlers are not called before 'then' returns."); +} + +function promiseAsync_ResolveThenTimeout() { + var handlerExecuted = false; + + Promise.resolve().then(function() { + handlerExecuted = true; + }); + + setTimeout(function() { + ok(handlerExecuted, "Handler should have been called before the timeout."); + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }, 0); + + ok(!handlerExecuted, "Handlers are not called before 'then' returns."); +} + +function promiseAsync_SyncXHR() +{ + var handlerExecuted = false; + + Promise.resolve().then(function() { + handlerExecuted = true; + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }); + + ok(!handlerExecuted, "Handlers are not called until the next microtask."); + + var xhr = new XMLHttpRequest(); + xhr.open("GET", "testXHR.txt", false); + xhr.send(null); + + todo(!handlerExecuted, "Sync XHR should not trigger microtask execution."); +} + +function promiseDoubleThen() { + var steps = 0; + var promise = new Promise(function(r1, r2) { + r1(42); + }); + + promise.then(function(what) { + ok(true, "Then.resolve has been called"); + is(what, 42, "Value == 42"); + steps++; + }, function(what) { + ok(false, "Then.reject has been called"); + }); + + promise.then(function(what) { + ok(true, "Then.resolve has been called"); + is(steps, 1, "Then.resolve - step == 1"); + is(what, 42, "Value == 42"); + runTest(); + }, function(what) { + ok(false, "Then.reject has been called"); + }); +} + +function promiseThenException() { + var promise = new Promise(function(resolve, reject) { + resolve(42); + }); + + promise.then(function(what) { + ok(true, "Then.resolve has been called"); + throw "booh"; + }).catch(function(e) { + ok(true, "window.onerror has been called!"); + runTest(); + }); +} + +function promiseThenCatchThen() { + var promise = new Promise(function(resolve, reject) { + resolve(42); + }); + + var promise2 = promise.then(function(what) { + ok(true, "Then.resolve has been called"); + is(what, 42, "Value == 42"); + return what + 1; + }, function(what) { + ok(false, "Then.reject has been called"); + }); + + isnot(promise, promise2, "These 2 promise objs are different"); + + promise2.then(function(what) { + ok(true, "Then.resolve has been called"); + is(what, 43, "Value == 43"); + return what + 1; + }, function(what) { + ok(false, "Then.reject has been called"); + }).catch(function() { + ok(false, "Catch has been called"); + }).then(function(what) { + ok(true, "Then.resolve has been called"); + is(what, 44, "Value == 44"); + runTest(); + }, function(what) { + ok(false, "Then.reject has been called"); + }); +} + +function promiseThenNoArg() { + var promise = new Promise(function(resolve, reject) { + resolve(42); + }); + + var clone = promise.then(); + isnot(promise, clone, "These 2 promise objs are different"); + promise.then(function(v) { + clone.then(function(cv) { + is(v, cv, "Both resolve to the same value"); + runTest(); + }); + }); +} + +function promiseThenUndefinedResolveFunction() { + var promise = new Promise(function(resolve, reject) { + reject(42); + }); + + try { + promise.then(undefined, function(v) { + is(v, 42, "Promise rejected with 42"); + runTest(); + }); + } catch (e) { + ok(false, "then should not throw on undefined resolve function"); + } +} + +function promiseThenNullResolveFunction() { + var promise = new Promise(function(resolve, reject) { + reject(42); + }); + + try { + promise.then(null, function(v) { + is(v, 42, "Promise rejected with 42"); + runTest(); + }); + } catch (e) { + ok(false, "then should not throw on null resolve function"); + } +} + +function promiseRejectThenCatchThen() { + var promise = new Promise(function(resolve, reject) { + reject(42); + }); + + var promise2 = promise.then(function(what) { + ok(false, "Then.resolve has been called"); + }, function(what) { + ok(true, "Then.reject has been called"); + is(what, 42, "Value == 42"); + return what + 1; + }); + + isnot(promise, promise2, "These 2 promise objs are different"); + + promise2.then(function(what) { + ok(true, "Then.resolve has been called"); + is(what, 43, "Value == 43"); + return what+1; + }).catch(function(what) { + ok(false, "Catch has been called"); + }).then(function(what) { + ok(true, "Then.resolve has been called"); + is(what, 44, "Value == 44"); + runTest(); + }); +} + +function promiseRejectThenCatchThen2() { + var promise = new Promise(function(resolve, reject) { + reject(42); + }); + + promise.then(function(what) { + ok(true, "Then.resolve has been called"); + is(what, 42, "Value == 42"); + return what+1; + }).catch(function(what) { + is(what, 42, "Value == 42"); + ok(true, "Catch has been called"); + return what+1; + }).then(function(what) { + ok(true, "Then.resolve has been called"); + is(what, 43, "Value == 43"); + runTest(); + }); +} + +function promiseRejectThenCatchExceptionThen() { + var promise = new Promise(function(resolve, reject) { + reject(42); + }); + + promise.then(function(what) { + ok(false, "Then.resolve has been called"); + }, function(what) { + ok(true, "Then.reject has been called"); + is(what, 42, "Value == 42"); + throw(what + 1); + }).catch(function(what) { + ok(true, "Catch has been called"); + is(what, 43, "Value == 43"); + return what + 1; + }).then(function(what) { + ok(true, "Then.resolve has been called"); + is(what, 44, "Value == 44"); + runTest(); + }); +} + +function promiseThenCatchOrderingResolve() { + var global = 0; + var f = new Promise(function(r1, r2) { + r1(42); + }); + + f.then(function() { + f.then(function() { + global++; + }); + f.catch(function() { + global++; + }); + f.then(function() { + global++; + }); + setTimeout(function() { + is(global, 2, "Many steps... should return 2"); + runTest(); + }, 0); + }); +} + +function promiseThenCatchOrderingReject() { + var global = 0; + var f = new Promise(function(r1, r2) { + r2(42); + }) + + f.then(function() {}, function() { + f.then(function() { + global++; + }); + f.catch(function() { + global++; + }); + f.then(function() {}, function() { + global++; + }); + setTimeout(function() { + is(global, 2, "Many steps... should return 2"); + runTest(); + }, 0); + }); +} + +function promiseCatchNoArg() { + var promise = new Promise(function(resolve, reject) { + reject(42); + }); + + var clone = promise.catch(); + isnot(promise, clone, "These 2 promise objs are different"); + promise.catch(function(v) { + clone.catch(function(cv) { + is(v, cv, "Both reject to the same value"); + runTest(); + }); + }); +} + +function promiseNestedPromise() { + new Promise(function(resolve, reject) { + resolve(new Promise(function(resolve, reject) { + ok(true, "Nested promise is executed"); + resolve(42); + })); + }).then(function(value) { + is(value, 42, "Nested promise is executed and then == 42"); + runTest(); + }); +} + +function promiseNestedNestedPromise() { + new Promise(function(resolve, reject) { + resolve(new Promise(function(resolve, reject) { + ok(true, "Nested promise is executed"); + resolve(42); + }).then(function(what) { return what+1; })); + }).then(function(value) { + is(value, 43, "Nested promise is executed and then == 43"); + runTest(); + }); +} + +function promiseWrongNestedPromise() { + new Promise(function(resolve, reject) { + resolve(new Promise(function(r, r2) { + ok(true, "Nested promise is executed"); + r(42); + })); + reject(42); + }).then(function(value) { + is(value, 42, "Nested promise is executed and then == 42"); + runTest(); + }, function(value) { + ok(false, "This is wrong"); + }); +} + +function promiseLoop() { + new Promise(function(resolve, reject) { + resolve(new Promise(function(r1, r2) { + ok(true, "Nested promise is executed"); + r1(new Promise(function(r1, r2) { + ok(true, "Nested nested promise is executed"); + r1(42); + })); + })); + }).then(function(value) { + is(value, 42, "Nested nested promise is executed and then == 42"); + runTest(); + }, function(value) { + ok(false, "This is wrong"); + }); +} + +function promiseStaticReject() { + var promise = Promise.reject(42).then(function(what) { + ok(false, "This should not be called"); + }, function(what) { + is(what, 42, "Value == 42"); + runTest(); + }); +} + +function promiseStaticResolve() { + var promise = Promise.resolve(42).then(function(what) { + is(what, 42, "Value == 42"); + runTest(); + }, function() { + ok(false, "This should not be called"); + }); +} + +function promiseResolveNestedPromise() { + var promise = Promise.resolve(new Promise(function(r, r2) { + ok(true, "Nested promise is executed"); + r(42); + }, function() { + ok(false, "This should not be called"); + })).then(function(what) { + is(what, 42, "Value == 42"); + runTest(); + }, function() { + ok(false, "This should not be called"); + }); +} + +function promiseSimpleThenableResolve() { + var thenable = { then: function(resolve) { resolve(5); } }; + var promise = new Promise(function(resolve, reject) { + resolve(thenable); + }); + + promise.then(function(v) { + ok(v === 5, "promiseSimpleThenableResolve"); + runTest(); + }, function(e) { + ok(false, "promiseSimpleThenableResolve: Should not reject"); + }); +} + +function promiseSimpleThenableReject() { + var thenable = { then: function(resolve, reject) { reject(5); } }; + var promise = new Promise(function(resolve, reject) { + resolve(thenable); + }); + + promise.then(function() { + ok(false, "promiseSimpleThenableReject: Should not resolve"); + runTest(); + }, function(e) { + ok(e === 5, "promiseSimpleThenableReject"); + runTest(); + }); +} + +function promiseThenableThrowsBeforeCallback() { + var thenable = { then: function(resolve) { + throw new TypeError("Hi there"); + resolve(5); + }}; + + var promise = Promise.resolve(thenable); + promise.then(function(v) { + ok(false, "promiseThenableThrowsBeforeCallback: Should've rejected"); + runTest(); + }, function(e) { + ok(e instanceof TypeError, "promiseThenableThrowsBeforeCallback"); + runTest(); + }); +} + +function promiseThenableThrowsAfterCallback() { + var thenable = { then: function(resolve) { + resolve(5); + throw new TypeError("Hi there"); + }}; + + var promise = Promise.resolve(thenable); + promise.then(function(v) { + ok(v === 5, "promiseThenableThrowsAfterCallback"); + runTest(); + }, function(e) { + ok(false, "promiseThenableThrowsAfterCallback: Should've resolved"); + runTest(); + }); +} + +function promiseThenableRejectThenResolve() { + var thenable = { then: function(resolve, reject) { + reject(new TypeError("Hi there")); + resolve(5); + }}; + + var promise = Promise.resolve(thenable); + promise.then(function(v) { + ok(false, "promiseThenableRejectThenResolve should have rejected"); + runTest(); + }, function(e) { + ok(e instanceof TypeError, "promiseThenableRejectThenResolve"); + runTest(); + }); +} + +function promiseWithThenReplaced() { + // Ensure that we call the 'then' on the promise and not the internal then. + var promise = new Promise(function(resolve, reject) { + resolve(5); + }); + + // Rogue `then` always rejects. + promise.then = function(onFulfill, onReject) { + onReject(new TypeError("Foo")); + } + + var promise2 = Promise.resolve(promise); + promise2.then(function(v) { + ok(false, "promiseWithThenReplaced: Should've rejected"); + runTest(); + }, function(e) { + ok(e instanceof TypeError, "promiseWithThenReplaced"); + runTest(); + }); +} + +function promiseStrictHandlers() { + var promise = Promise.resolve(5); + promise.then(function() { + "use strict"; + ok(this === undefined, "Strict mode callback should have this === undefined."); + runTest(); + }); +} + +function promiseStrictExecutorThisArg() { + var promise = new Promise(function(resolve, reject) { + "use strict"; + ok(this === undefined, "thisArg should be undefined."); + runTest(); + }); +} + +function promiseResolveArray() { + var p = Promise.resolve([1,2,3]); + ok(p instanceof Promise, "Should return a Promise."); + p.then(function(v) { + ok(Array.isArray(v), "Resolved value should be an Array"); + is(v.length, 3, "Length should match"); + is(v[0], 1, "Resolved value should match original"); + is(v[1], 2, "Resolved value should match original"); + is(v[2], 3, "Resolved value should match original"); + runTest(); + }); +} + +function promiseResolveThenable() { + var p = Promise.resolve({ then: function(onFulfill, onReject) { onFulfill(2); } }); + ok(p instanceof Promise, "Should cast to a Promise."); + p.then(function(v) { + is(v, 2, "Should resolve to 2."); + runTest(); + }, function(e) { + ok(false, "promiseResolveThenable should've resolved"); + runTest(); + }); +} + +function promiseResolvePromise() { + var original = Promise.resolve(true); + var cast = Promise.resolve(original); + + ok(cast instanceof Promise, "Should cast to a Promise."); + is(cast, original, "Should return original Promise."); + cast.then(function(v) { + is(v, true, "Should resolve to true."); + runTest(); + }); +} + +// Bug 1009569. +// Ensure that thenables are run on a clean stack asynchronously. +// Test case adopted from +// https://gist.github.com/getify/d64bb01751b50ed6b281#file-bug1-js. +function promiseResolveThenableCleanStack() { + function immed(s) { x++; s(); } + function incX(){ x++; } + + var x = 0; + var thenable = { then: immed }; + var results = []; + + var p = Promise.resolve(thenable).then(incX); + results.push(x); + + // check what happens after all "next cycle" steps + // have had a chance to complete + setTimeout(function(){ + // Result should be [0, 2] since `thenable` will be called async. + is(results[0], 0, "Expected thenable to be called asynchronously"); + // See Bug 1023547 comment 13 for why this check has to be gated on p. + p.then(function() { + results.push(x); + is(results[1], 2, "Expected thenable to be called asynchronously"); + runTest(); + }); + },1000); +} + +// Bug 1008467 - Promise fails with "too much recursion". +// The bug was that the callbacks passed to a thenable would resolve the +// promise synchronously when the fulfill handler returned a non-thenable. +// +// For example: +// var p = new Promise(function(resolve) { +// resolve(5); +// }); +// var m = Promise.resolve(p); +// +// At this point `m` is a Promise that is resolved with a thenable `p`, so it +// calls `p.then()` with two callbacks, both of which would synchronously resolve +// `m` when `p` invoked them (on account of itself being resolved, possibly +// synchronously. A chain of these 'Promise resolved by a Promise' would lead to +// stack overflow. +function promiseTestAsyncThenableResolution() +{ + var k = 3000; + Promise.resolve().then(function next() { + k--; + if (k > 0) return Promise.resolve().then(next); + }).then(function () { + ok(true, "Resolution of a chain of thenables should not be synchronous."); + runTest(); + }); +} + +// Bug 1062323 +function promiseWrapperAsyncResolution() +{ + var p = new Promise(function(resolve, reject){ + resolve(); + }); + + var results = []; + var q = p.then(function () { + results.push("1-1"); + }).then(function () { + results.push("1-2"); + }).then(function () { + results.push("1-3"); + }); + + var r = p.then(function () { + results.push("2-1"); + }).then(function () { + results.push("2-2"); + }).then(function () { + results.push("2-3"); + }); + + Promise.all([q, r]).then(function() { + var match = results[0] == "1-1" && + results[1] == "2-1" && + results[2] == "1-2" && + results[3] == "2-2" && + results[4] == "1-3" && + results[5] == "2-3"; + info(results); + ok(match, "Chained promises should resolve asynchronously."); + runTest(); + }, function() { + ok(false, "promiseWrapperAsyncResolution: One of the promises failed."); + runTest(); + }); +} + +var tests = [ promiseResolve, promiseReject, + promiseException, promiseGC, + promiseAsync_TimeoutResolveThen, + promiseAsync_ResolveTimeoutThen, + promiseAsync_ResolveThenTimeout, + promiseAsync_SyncXHR, + promiseDoubleThen, promiseThenException, + promiseThenCatchThen, promiseRejectThenCatchThen, + promiseRejectThenCatchThen2, + promiseRejectThenCatchExceptionThen, + promiseThenCatchOrderingResolve, + promiseThenCatchOrderingReject, + promiseNestedPromise, promiseNestedNestedPromise, + promiseWrongNestedPromise, promiseLoop, + promiseStaticReject, promiseStaticResolve, + promiseResolveNestedPromise, + promiseResolveNoArg, + promiseRejectNoArg, + promiseThenNoArg, + promiseThenUndefinedResolveFunction, + promiseThenNullResolveFunction, + promiseCatchNoArg, + promiseRejectNoHandler, + promiseSimpleThenableResolve, + promiseSimpleThenableReject, + promiseThenableThrowsBeforeCallback, + promiseThenableThrowsAfterCallback, + promiseThenableRejectThenResolve, + promiseWithThenReplaced, + promiseStrictHandlers, + promiseStrictExecutorThisArg, + promiseResolveArray, + promiseResolveThenable, + promiseResolvePromise, + promiseResolveThenableCleanStack, + promiseTestAsyncThenableResolution, + promiseWrapperAsyncResolution, + ]; + +function runTest() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +runTest(); +// --> +</script> +</pre> +</body> +</html> + diff --git a/dom/promise/tests/test_promise_and_timeout_ordering.html b/dom/promise/tests/test_promise_and_timeout_ordering.html new file mode 100644 index 000000000..1dfa13bbb --- /dev/null +++ b/dom/promise/tests/test_promise_and_timeout_ordering.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for promise and timeout ordering</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test("Promise callbacks should run immediately after the setTimeout handler that enqueues them"); +var origPostMessage = window.postMessage; +window.postMessage = function(msg) { origPostMessage.call(window, msg, "*"); } +window.onmessage = t.step_func_done(function(e) { + assert_equals(e.data, "t1start, t1end, promise, t2"); +}); +</script> +<script src="file_promise_and_timeout_ordering.js"></script> diff --git a/dom/promise/tests/test_promise_and_timeout_ordering_workers.html b/dom/promise/tests/test_promise_and_timeout_ordering_workers.html new file mode 100644 index 000000000..122a794db --- /dev/null +++ b/dom/promise/tests/test_promise_and_timeout_ordering_workers.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for promise and timeout ordering in workers</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test("Promise callbacks in workers should run immediately after the setTimeout handler that enqueues them"); +var w = new Worker("file_promise_and_timeout_ordering.js"); +w.onmessage = t.step_func_done(function(e) { + assert_equals(e.data, "t1start, t1end, promise, t2"); +}); +</script> diff --git a/dom/promise/tests/test_promise_uncatchable_exception.html b/dom/promise/tests/test_promise_uncatchable_exception.html new file mode 100644 index 000000000..8f7167d56 --- /dev/null +++ b/dom/promise/tests/test_promise_uncatchable_exception.html @@ -0,0 +1,35 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>Promise - uncatchable exceptions</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +onmessage = function(evt) { + ok(true, "finished"); + SimpleTest.finish(); +} + +function go() { + var script = document.createElement("script"); + script.src = "promise_uncatchable_exception.js"; + document.body.appendChild(script); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, go); +</script> +</pre> +</body> +</html> diff --git a/dom/promise/tests/test_promise_utils.html b/dom/promise/tests/test_promise_utils.html new file mode 100644 index 000000000..316ea02a5 --- /dev/null +++ b/dom/promise/tests/test_promise_utils.html @@ -0,0 +1,320 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>Test for Promise.all, Promise.race</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"><!-- + +function promiseUtilitiesDefined() { + ok(Promise.all, "Promise.all must be defined when Promise is enabled."); + ok(Promise.race, "Promise.race must be defined when Promise is enabled."); + runTest(); +} + +function promiseAllEmptyArray() { + var p = Promise.all([]); + ok(p instanceof Promise, "Return value of Promise.all should be a Promise."); + p.then(function(values) { + ok(Array.isArray(values), "Resolved value should be an array."); + is(values.length, 0, "Resolved array length should match iterable's length."); + runTest(); + }, function() { + ok(false, "Promise.all shouldn't fail when iterable has no rejected Promises."); + runTest(); + }); +} + +function promiseAllArray() { + var p = Promise.all([1, new Date(), Promise.resolve("firefox")]); + ok(p instanceof Promise, "Return value of Promise.all should be a Promise."); + p.then(function(values) { + ok(Array.isArray(values), "Resolved value should be an array."); + is(values.length, 3, "Resolved array length should match iterable's length."); + is(values[0], 1, "Array values should match."); + ok(values[1] instanceof Date, "Array values should match."); + is(values[2], "firefox", "Array values should match."); + runTest(); + }, function() { + ok(false, "Promise.all shouldn't fail when iterable has no rejected Promises."); + runTest(); + }); +} + +function promiseAllIterable() { + function* promiseGen() { + var i = 3; + while (--i) { + yield Promise.resolve(i); + } + + yield new Promise(function(resolve) { + setTimeout(resolve, 10); + }); + } + + Promise.all(promiseGen()).then(function(values) { + is(values.length, 3, "Resolved array length should match iterable's length."); + is(values[0], 2, "Array values should match."); + is(values[1], 1, "Array values should match."); + is(values[2], undefined, "Array values should match."); + runTest(); + }, function(e) { + ok(false, "Promise.all shouldn't fail when an iterable is passed."); + runTest(); + }); +} + +function promiseAllWaitsForAllPromises() { + var arr = [ + new Promise(function(resolve) { + setTimeout(resolve.bind(undefined, 1), 50); + }), + new Promise(function(resolve) { + setTimeout(resolve.bind(undefined, 2), 10); + }), + new Promise(function(resolve) { + setTimeout(resolve.bind(undefined, new Promise(function(resolve2) { + resolve2(3); + })), 10); + }), + new Promise(function(resolve) { + setTimeout(resolve.bind(undefined, 4), 20); + }) + ]; + + var p = Promise.all(arr); + p.then(function(values) { + ok(Array.isArray(values), "Resolved value should be an array."); + is(values.length, 4, "Resolved array length should match iterable's length."); + is(values[0], 1, "Array values should match."); + is(values[1], 2, "Array values should match."); + is(values[2], 3, "Array values should match."); + is(values[3], 4, "Array values should match."); + runTest(); + }, function() { + ok(false, "Promise.all shouldn't fail when iterable has no rejected Promises."); + runTest(); + }); +} + +function promiseAllRejectFails() { + var arr = [ + new Promise(function(resolve) { + setTimeout(resolve.bind(undefined, 1), 50); + }), + new Promise(function(resolve, reject) { + setTimeout(reject.bind(undefined, 2), 10); + }), + new Promise(function(resolve) { + setTimeout(resolve.bind(undefined, 3), 10); + }), + new Promise(function(resolve) { + setTimeout(resolve.bind(undefined, 4), 20); + }) + ]; + + var p = Promise.all(arr); + p.then(function(values) { + ok(false, "Promise.all shouldn't resolve when iterable has rejected Promises."); + runTest(); + }, function(e) { + ok(true, "Promise.all should reject when iterable has rejected Promises."); + is(e, 2, "Rejection value should match."); + runTest(); + }); +} + +function promiseAllCastError() { + var p = Promise.all([Promise.resolve(2), { then: function() { foo(); } }]); + ok(p instanceof Promise, "Should cast to a Promise."); + p.then(function(v) { + ok(false, "promiseAllCastError: should've rejected."); + runTest(); + }, function(e) { + ok(e instanceof ReferenceError, "promiseCastThenableError"); + runTest(); + }); +} + +// Check that the resolved array is enumerable. +function promiseAllEnumerable() { + var p = Promise.all([1, new Date(), Promise.resolve("firefox")]); + p.then(function(v) { + var count = 0; + for (key in v) { + ++count; + ok(v[key] === 1 || v[key] instanceof Date || v[key] === "firefox", + "Enumerated properties don't match."); + } + is(count, 3, "Resolved array from Promise.all should be enumerable"); + runTest(); + }, function(e) { + ok(false, "promiseAllEnumerable: should've resolved."); + runTest(); + }); +} + +function promiseRaceEmpty() { + var p = Promise.race([]); + ok(p instanceof Promise, "Should return a Promise."); + p.then(function() { + ok(false, "Should not resolve"); + }, function() { + ok(false, "Should not reject"); + }); + // Per spec, An empty race never resolves or rejects. + setTimeout(function() { + ok(true); + runTest(); + }, 50); +} + +function promiseRaceValuesArray() { + var p = Promise.race([true, new Date(), 3]); + ok(p instanceof Promise, "Should return a Promise."); + p.then(function(winner) { + is(winner, true, "First value should win."); + runTest(); + }, function(err) { + ok(false, "Should not fail " + err + "."); + runTest(); + }); +} + +function promiseRacePromiseArray() { + function timeoutPromise(n) { + return new Promise(function(resolve) { + setTimeout(function() { + resolve(n); + }, n); + }); + } + + var arr = [ + new Promise(function(resolve) { + resolve("first"); + }), + Promise.resolve("second"), + new Promise(function() {}), + new Promise(function(resolve) { + setTimeout(function() { + setTimeout(function() { + resolve("fourth"); + }, 0); + }, 0); + }), + ]; + + var p = Promise.race(arr); + p.then(function(winner) { + is(winner, "first", "First queued resolution should win the race."); + runTest(); + }); +} + +function promiseRaceIterable() { + function* participants() { + yield new Promise(function(resolve) { + setTimeout(resolve, 10, 10); + }); + yield new Promise(function(resolve) { + setTimeout(resolve, 20, 20); + }); + } + + Promise.race(participants()).then(function(winner) { + is(winner, 10, "Winner should be the one that finished earlier."); + runTest(); + }, function(e) { + ok(false, "Promise.race shouldn't throw when an iterable is passed!"); + runTest(); + }); +} + +function promiseRaceReject() { + var p = Promise.race([ + Promise.reject(new Error("Fail bad!")), + new Promise(function(resolve) { + setTimeout(resolve, 0); + }) + ]); + + p.then(function() { + ok(false, "Should not resolve when winning Promise rejected."); + runTest(); + }, function(e) { + ok(true, "Should be rejected"); + ok(e instanceof Error, "Should reject with Error."); + ok(e.message == "Fail bad!", "Message should match."); + runTest(); + }); +} + +function promiseRaceThrow() { + var p = Promise.race([ + new Promise(function(resolve) { + nonExistent(); + }), + new Promise(function(resolve) { + setTimeout(resolve, 0); + }) + ]); + + p.then(function() { + ok(false, "Should not resolve when winning Promise had an error."); + runTest(); + }, function(e) { + ok(true, "Should be rejected"); + ok(e instanceof ReferenceError, "Should reject with ReferenceError for function nonExistent()."); + runTest(); + }); +} + +var tests = [ + promiseUtilitiesDefined, + promiseAllEmptyArray, + promiseAllArray, + promiseAllIterable, + promiseAllWaitsForAllPromises, + promiseAllRejectFails, + promiseAllCastError, + promiseAllEnumerable, + + promiseRaceEmpty, + promiseRaceValuesArray, + promiseRacePromiseArray, + promiseRaceIterable, + promiseRaceReject, + promiseRaceThrow, + ]; + +function runTest() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +runTest(); +// --> +</script> +</pre> +</body> +</html> + diff --git a/dom/promise/tests/test_promise_xrays.html b/dom/promise/tests/test_promise_xrays.html new file mode 100644 index 000000000..55398167e --- /dev/null +++ b/dom/promise/tests/test_promise_xrays.html @@ -0,0 +1,365 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1170760 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1170760</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1170760">Mozilla Bug 1170760</a> +<p id="display"></p> +<div id="content" style="display: none"> +<iframe id="t" src="http://example.org/chrome/dom/promise/tests/file_promise_xrays.html"></iframe> +</div> + +<pre id="test"> +<script type="application/javascript"> + +var win = $("t").contentWindow; + +/** Test for Bug 1170760 **/ +SimpleTest.waitForExplicitFinish(); + +function testLoadComplete() { + is(win.location.href, $("t").src, "Should have loaded the right thing"); + nextTest(); +} + +function testHaveXray() { + is(typeof win.Promise.race, "function", "Should see a race() function"); + var exception; + try { + win.Promise.wrappedJSObject.race; + } catch (e) { + exception = e; + } + is(exception, "Getting race", "Should have thrown the right exception"); + is(win.wrappedJSObject.setupThrew, false, "Setup should not have thrown"); + nextTest(); +} + +function testConstructor1() { + var p = new win.Promise(function(resolve, reject) { resolve(win.Promise.resolve(5)); }); + p.then( + function(arg) { + is(arg, 5, "Content Promise constructor resolved with content promise should work"); + }, + function(e) { + ok(false, "Content Promise constructor resolved with content promise should not fail"); + } + ).then(nextTest); +} + +function testConstructor2() { + var p = new win.Promise(function(resolve, reject) { resolve(Promise.resolve(5)); }); + p.then( + function(arg) { + is(arg, 5, "Content Promise constructor resolved with chrome promise should work"); + }, + function(e) { + ok(false, "Content Promise constructor resolved with chrome promise should not fail"); + } + ).then(nextTest); +} + +function testRace1() { + var p = win.Promise.race(new win.Array(1, 2)); + p.then( + function(arg) { + ok(arg == 1 || arg == 2, + "Should get the right value when racing content-side array"); + }, + function(e) { + ok(false, "testRace1 threw exception: " + e); + } + ).then(nextTest); +} + +function testRace2() { + var p = win.Promise.race( + new Array(win.Promise.resolve(1), win.Promise.resolve(2))); + p.then( + function(arg) { + ok(arg == 1 || arg == 2, + "Should get the right value when racing content-side array of explicit Promises"); + }, + function(e) { + ok(false, "testRace2 threw exception: " + e); + } + ).then(nextTest); +} + +function testRace3() { + // This works with a chrome-side array because we do the iteration + // while still in the Xray compartment. + var p = win.Promise.race([1, 2]); + p.then( + function(arg) { + ok(arg == 1 || arg == 2, + "Should get the right value when racing chrome-side array"); + }, + function(e) { + ok(false, "testRace3 threw exception: " + e); + } + ).then(nextTest); +} + +function testRace4() { + // This works with both content-side and chrome-side Promises because we want + // it to and go to some lengths to make it work. + var p = win.Promise.race([Promise.resolve(1), win.Promise.resolve(2)]); + p.then( + function(arg) { + ok(arg == 1 || arg == 2, + "Should get the right value when racing chrome-side promises"); + }, + function(e) { + ok(false, "testRace4 threw exception: " + e); + } + ).then(nextTest); +} + +function testAll1() { + var p = win.Promise.all(new win.Array(1, 2)); + p.then( + function(arg) { + ok(arg instanceof win.Array, "Should get an Array from Promise.all (1)"); + is(arg[0], 1, "First entry of Promise.all return value should be correct (1)"); + is(arg[1], 2, "Second entry of Promise.all return value should be correct (1)"); + }, + function(e) { + ok(false, "testAll1 threw exception: " + e); + } + ).then(nextTest); +} + +function testAll2() { + var p = win.Promise.all( + new Array(win.Promise.resolve(1), win.Promise.resolve(2))); + p.then( + function(arg) { + ok(arg instanceof win.Array, "Should get an Array from Promise.all (2)"); + is(arg[0], 1, "First entry of Promise.all return value should be correct (2)"); + is(arg[1], 2, "Second entry of Promise.all return value should be correct (2)"); + }, + function(e) { + ok(false, "testAll2 threw exception: " + e); + } + ).then(nextTest); +} + +function testAll3() { + // This works with a chrome-side array because we do the iteration + // while still in the Xray compartment. + var p = win.Promise.all([1, 2]); + p.then( + function(arg) { + ok(arg instanceof win.Array, "Should get an Array from Promise.all (3)"); + is(arg[0], 1, "First entry of Promise.all return value should be correct (3)"); + is(arg[1], 2, "Second entry of Promise.all return value should be correct (3)"); + }, + function(e) { + ok(false, "testAll3 threw exception: " + e); + } + ).then(nextTest); +} + +function testAll4() { + // This works with both content-side and chrome-side Promises because we want + // it to and go to some lengths to make it work. + var p = win.Promise.all([Promise.resolve(1), win.Promise.resolve(2)]); + p.then( + function(arg) { + ok(arg instanceof win.Array, "Should get an Array from Promise.all (4)"); + is(arg[0], 1, "First entry of Promise.all return value should be correct (4)"); + is(arg[1], 2, "Second entry of Promise.all return value should be correct (4)"); + }, + function(e) { + ok(false, "testAll4 threw exception: " + e); + } + ).then(nextTest); +} + +function testAll5() { + var p = win.Promise.all(new win.Array()); + p.then( + function(arg) { + ok(arg instanceof win.Array, "Should get an Array from Promise.all (5)"); + }, + function(e) { + ok(false, "testAll5 threw exception: " + e); + } + ).then(nextTest); +} + +function testResolve1() { + var p = win.Promise.resolve(5); + ok(p instanceof win.Promise, "Promise.resolve should return a promise"); + p.then( + function(arg) { + is(arg, 5, "Should get correct Promise.resolve value"); + }, + function(e) { + ok(false, "testAll5 threw exception: " + e); + } + ).then(nextTest); +} + +function testResolve2() { + var p = win.Promise.resolve(5); + var q = win.Promise.resolve(p); + is(q, p, "Promise.resolve should just pass through Promise values"); + nextTest(); +} + +function testResolve3() { + var p = win.Promise.resolve(Promise.resolve(5)); + p.then( + function(arg) { + is(arg, 5, "Promise.resolve with chrome Promise should work"); + }, + function(e) { + ok(false, "Promise.resolve with chrome Promise should not fail"); + } + ).then(nextTest); +} + +function testResolve4() { + var p = new win.Promise((res, rej) => {}); + Components.utils.getJSTestingFunctions().resolvePromise(p, 42); + p.then( + function(arg) { + is(arg, 42, "Resolving an Xray to a promise with TestingFunctions resolvePromise should work"); + }, + function(e) { + ok(false, "Resolving an Xray to a promise with TestingFunctions resolvePromise should not fail"); + } + ).then(nextTest); +} + +function testReject1() { + var p = win.Promise.reject(5); + ok(p instanceof win.Promise, "Promise.reject should return a promise"); + p.then( + function(arg) { + ok(false, "Promise should be rejected"); + }, + function(e) { + is(e, 5, "Should get correct Promise.reject value"); + } + ).then(nextTest); +} + +function testReject2() { + var p = new win.Promise((res, rej) => {}); + Components.utils.getJSTestingFunctions().rejectPromise(p, 42); + p.then( + function(arg) { + ok(false, "Rejecting an Xray to a promise with TestingFunctions rejectPromise should trigger catch handler"); + }, + function(e) { + is(e, 42, "Rejecting an Xray to a promise with TestingFunctions rejectPromise should work"); + } + ).then(nextTest); +} + +function testThen1() { + var p = win.Promise.resolve(5); + var q = p.then((x) => x*x); + ok(q instanceof win.Promise, + "Promise.then should return a promise from the right global"); + q.then( + function(arg) { + is(arg, 25, "Promise.then should work"); + }, + function(e) { + ok(false, "Promise.then should not fail"); + } + ).then(nextTest); +} + +function testThen2() { + var p = win.Promise.resolve(5); + var q = p.then((x) => Promise.resolve(x*x)); + ok(q instanceof win.Promise, + "Promise.then should return a promise from the right global"); + q.then( + function(arg) { + is(arg, 25, "Promise.then resolved with chrome promise should work"); + }, + function(e) { + ok(false, "Promise.then resolved with chrome promise should not fail"); + } + ).then(nextTest); +} + +function testCatch1() { + var p = win.Promise.reject(5); + ok(p instanceof win.Promise, "Promise.reject should return a promise"); + var q = p.catch((x) => x*x); + ok(q instanceof win.Promise, + "Promise.catch should return a promise from the right global"); + q.then( + function(arg) { + is(arg, 25, "Promise.catch should work"); + }, + function(e) { + ok(false, "Promise.catch should not fail"); + } + ).then(nextTest); +} + +function testToStringTag1() { + is(win.Promise.prototype[Symbol.toStringTag], "Promise", "@@toStringTag was incorrect"); + var p = win.Promise.resolve(); + is(String(p), "[object Promise]", "String() result was incorrect"); + is(p.toString(), "[object Promise]", "toString result was incorrect"); + is(Object.prototype.toString.call(p), "[object Promise]", "second toString result was incorrect"); + nextTest(); +} + +var tests = [ + testLoadComplete, + testHaveXray, + testConstructor1, + testConstructor2, + testRace1, + testRace2, + testRace3, + testRace4, + testAll1, + testAll2, + testAll3, + testAll4, + testAll5, + testResolve1, + testResolve2, + testResolve3, + testResolve4, + testReject1, + testReject2, + testThen1, + testThen2, + testCatch1, + testToStringTag1, +]; + +function nextTest() { + if (tests.length == 0) { + SimpleTest.finish(); + return; + } + tests.shift()(); +} + +addLoadEvent(nextTest); + +</script> +</pre> +</body> +</html> diff --git a/dom/promise/tests/test_resolve.html b/dom/promise/tests/test_resolve.html new file mode 100644 index 000000000..780eae6c6 --- /dev/null +++ b/dom/promise/tests/test_resolve.html @@ -0,0 +1,67 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>Promise.resolve(anything) Test</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"><!-- + +var tests = [ + null, + 42, + "hello world", + true, + false, + {}, + { a: 42 }, + [ 1, 2, 3, 4, null, true, "hello world" ], + function() {}, + window, + undefined, + document.createElement('input'), + new Date(), +]; + +function cbError() { + ok(false, "Nothing should arrive here!"); +} + +function runTest() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.pop(); + + new Promise(function(resolve, reject) { + resolve(test); + }).then(function(what) { + ok(test === what, "What is: " + what); + }, cbError).then(function() { + new Promise(function(resolve, reject) { + reject(test) + }).then(cbError, function(what) { + ok(test === what, "What is: " + what); + }).then(runTest, cbError); + }); +} + +SimpleTest.waitForExplicitFinish(); +runTest(); +// --> +</script> +</pre> +</body> +</html> + diff --git a/dom/promise/tests/test_resolver_return_value.html b/dom/promise/tests/test_resolver_return_value.html new file mode 100644 index 000000000..1fb9652ac --- /dev/null +++ b/dom/promise/tests/test_resolver_return_value.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1120235 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1120235</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1120235 **/ + var res, rej; + var p = new Promise(function(resolve, reject) { res = resolve; rej = reject; }); + is(res(1), undefined, "Resolve function should return undefined"); + is(rej(2), undefined, "Reject function should return undefined"); + + var thenable = { + then: function(resolve, reject) { + is(resolve(3), undefined, "Thenable resolve argument should return undefined"); + is(reject(4), undefined, "Thenable reject argument should return undefined"); + SimpleTest.finish(); + } + }; + + SimpleTest.waitForExplicitFinish(); + p = Promise.resolve(thenable); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1120235">Mozilla Bug 1120235</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/promise/tests/test_species_getter.html b/dom/promise/tests/test_species_getter.html new file mode 100644 index 000000000..04e590a13 --- /dev/null +++ b/dom/promise/tests/test_species_getter.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for ...</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + var desc = Object.getOwnPropertyDescriptor(Promise, Symbol.species); + assert_not_equals(desc, undefined, "Should have a property"); + + assert_equals(desc.configurable, true, "Property should be configurable"); + assert_equals(desc.enumerable, false, "Property should not be enumerable"); + assert_equals(desc.set, undefined, "Should not have a setter"); + var getter = desc.get; + + var things = [undefined, null, 5, "xyz", Promise, Object]; + for (var thing of things) { + assert_equals(getter.call(thing), thing, + "Getter should return its this value"); + } + +}, "Promise should have an @@species getter that works per spec"); +</script> diff --git a/dom/promise/tests/test_thenable_vs_promise_ordering.html b/dom/promise/tests/test_thenable_vs_promise_ordering.html new file mode 100644 index 000000000..161e95d75 --- /dev/null +++ b/dom/promise/tests/test_thenable_vs_promise_ordering.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for promise resolution ordering with thenables and promises</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test("A promise resolved first (with a thenable) should trigger its callbacks before a promise resolved second (with a promise)."); +t.step(function() { + var customThenCalled = false; + var p0 = Promise.resolve(); + p0.then = function(resolved, rejected) { + customThenCalled = true; + Promise.prototype.then.call(this, resolved, rejected); + } + var p1 = new Promise(function(r) { r(p0); }); + delete p0.then; + var p2 = new Promise(function(r) { r(p0); }); + var resolutionOrder = ""; + Promise.all([ p1.then(function() { resolutionOrder += "1"; }), + p2.then(function() { resolutionOrder += "2"; }) ]) + .then(t.step_func_done(function() { + assert_true(customThenCalled, "Should have called custom then"); + assert_equals(resolutionOrder, "12"); + })); +}); +</script> diff --git a/dom/promise/tests/test_webassembly_compile.html b/dom/promise/tests/test_webassembly_compile.html new file mode 100644 index 000000000..0243df49c --- /dev/null +++ b/dom/promise/tests/test_webassembly_compile.html @@ -0,0 +1,174 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>WebAssembly.compile Test</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<script> +const wasmTextToBinary = SpecialPowers.unwrap(SpecialPowers.Cu.getJSTestingFunctions().wasmTextToBinary); +const wasmIsSupported = SpecialPowers.Cu.getJSTestingFunctions().wasmIsSupported +const fooModuleCode = wasmTextToBinary(`(module + (func $foo (result i32) (i32.const 42)) + (export "foo" $foo) +)`, 'new-format'); + +function checkFooModule(m) { + ok(m instanceof WebAssembly.Module, "got a module"); + var i = new WebAssembly.Instance(m); + ok(i instanceof WebAssembly.Instance, "got an instance"); + ok(i.exports.foo() === 42, "got 42"); +} + +function checkFooInstance(i) { + ok(i instanceof WebAssembly.Instance, "got a module"); + ok(i.exports.foo() === 42, "got 42"); +} + +function propertiesExist() { + if (!wasmIsSupported()) { + ok(!this["WebAssembly"], "If the device doesn't support, there will be no WebAssembly object"); + SimpleTest.finish(); + return; + } + + ok(WebAssembly, "WebAssembly object should exist"); + ok(WebAssembly.compile, "WebAssembly.compile function should exist"); + runTest(); +} + +function compileFail() { + WebAssembly.compile().then( + () => { ok(false, "should have failed"); runTest() } + ).catch( + err => { ok(err instanceof TypeError, "empty compile failed"); runTest() } + ); +} + +function compileSuccess() { + WebAssembly.compile(fooModuleCode).then( + m => { checkFooModule(m); runTest() } + ).catch( + err => { ok(false, String(err)); runTest() } + ); +} + +function compileManySuccess() { + const N = 100; + + var arr = []; + for (var i = 0; i < N; i++) + arr.push(WebAssembly.compile(fooModuleCode)); + + SpecialPowers.gc(); + + Promise.all(arr).then (ms => { + ok(ms.length === N, "got the right number"); + for (var i = 0; i < N; i++) + checkFooModule(ms[i]); + runTest(); + }).catch( + err => { ok(false, String(err)); runTest() } + ); +} + +function compileInWorker() { + var w = new Worker(`data:text/plain, + onmessage = e => { + WebAssembly.compile(e.data).then(m => { + var i = new WebAssembly.Instance(m); + if (i.exports.foo() !== 42) + throw "bad i.exports.foo() result"; + postMessage("ok"); + close(); + }).catch(err => { throw err }); + } + `); + w.postMessage(fooModuleCode); + w.onmessage = e => { + ok(e.data === "ok", "worker test"); + runTest(); + } +} + +function terminateCompileInWorker() { + var w = new Worker(`data:text/plain, + var fooModuleCode; + function spawnWork() { + const N = 100; + var arr = []; + for (var i = 0; i < N; i++) + arr.push(WebAssembly.compile(fooModuleCode)); + Promise.all(arr).then(spawnWork); + } + onmessage = e => { + fooModuleCode = e.data; + spawnWork(); + postMessage("ok"); + } + `); + w.postMessage(fooModuleCode); + w.onmessage = e => { + ok(e.data === "ok", "worker finished first step"); + w.terminate(); + runTest(); + } +} + +function instantiateFail() { + WebAssembly.instantiate().then( + () => { ok(false, "should have failed"); runTest() } + ).catch( + err => { ok(err instanceof TypeError, "empty compile failed"); runTest() } + ); +} + +function instantiateSuccess() { + WebAssembly.instantiate(fooModuleCode).then( + r => { checkFooModule(r.module); checkFooInstance(r.instance); runTest() } + ).catch( + err => { ok(false, String(err)); runTest() } + ); +} + +function chainSuccess() { + WebAssembly.compile(fooModuleCode).then( + m => WebAssembly.instantiate(m) + ).then( + i => { checkFooInstance(i); runTest() } + ).catch( + err => { ok(false, String(err)); runTest() } + ); +} + +var tests = [ propertiesExist, + compileFail, + compileSuccess, + compileManySuccess, + compileInWorker, + terminateCompileInWorker, + instantiateFail, + instantiateSuccess, + chainSuccess + ]; + +function runTest() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({"set": [["javascript.options.wasm", true]]}, runTest); +</script> +</body> +</html> + diff --git a/dom/promise/tests/unit/test_monitor_uncaught.js b/dom/promise/tests/unit/test_monitor_uncaught.js new file mode 100644 index 000000000..7dd80d212 --- /dev/null +++ b/dom/promise/tests/unit/test_monitor_uncaught.js @@ -0,0 +1,274 @@ +/* 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: Cu } = Components; + +Cu.import("resource://gre/modules/Timer.jsm", this); +Cu.import("resource://testing-common/PromiseTestUtils.jsm", this); + +// Prevent test failures due to the unhandled rejections in this test file. +PromiseTestUtils.disableUncaughtRejectionObserverForSelfTest(); + +add_task(function* test_globals() { + Assert.equal(Promise.defer || undefined, undefined, "We are testing DOM Promise."); + Assert.notEqual(PromiseDebugging, undefined, "PromiseDebugging is available."); +}); + +add_task(function* test_promiseID() { + let p1 = new Promise(resolve => {}); + let p2 = new Promise(resolve => {}); + let p3 = p2.then(null, null); + let promise = [p1, p2, p3]; + + let identifiers = promise.map(PromiseDebugging.getPromiseID); + do_print("Identifiers: " + JSON.stringify(identifiers)); + let idSet = new Set(identifiers); + Assert.equal(idSet.size, identifiers.length, + "PromiseDebugging.getPromiseID returns a distinct id per promise"); + + let identifiers2 = promise.map(PromiseDebugging.getPromiseID); + Assert.equal(JSON.stringify(identifiers), + JSON.stringify(identifiers2), + "Successive calls to PromiseDebugging.getPromiseID return the same id for the same promise"); +}); + +add_task(function* test_observe_uncaught() { + // The names of Promise instances + let names = new Map(); + + // The results for UncaughtPromiseObserver callbacks. + let CallbackResults = function(name) { + this.name = name; + this.expected = new Set(); + this.observed = new Set(); + this.blocker = new Promise(resolve => this.resolve = resolve); + }; + CallbackResults.prototype = { + observe: function(promise) { + do_print(this.name + " observing Promise " + names.get(promise)); + Assert.equal(PromiseDebugging.getState(promise).state, "rejected", + this.name + " observed a rejected Promise"); + if (!this.expected.has(promise)) { + Assert.ok(false, + this.name + " observed a Promise that it expected to observe, " + + names.get(promise) + + " (" + PromiseDebugging.getPromiseID(promise) + + ", " + PromiseDebugging.getAllocationStack(promise) + ")"); + + } + Assert.ok(this.expected.delete(promise), + this.name + " observed a Promise that it expected to observe, " + + names.get(promise) + " (" + PromiseDebugging.getPromiseID(promise) + ")"); + Assert.ok(!this.observed.has(promise), + this.name + " observed a Promise that it has not observed yet"); + this.observed.add(promise); + if (this.expected.size == 0) { + this.resolve(); + } else { + do_print(this.name + " is still waiting for " + this.expected.size + " observations:"); + do_print(JSON.stringify(Array.from(this.expected.values(), (x) => names.get(x)))); + } + }, + }; + + let onLeftUncaught = new CallbackResults("onLeftUncaught"); + let onConsumed = new CallbackResults("onConsumed"); + + let observer = { + onLeftUncaught: function(promise, data) { + onLeftUncaught.observe(promise); + }, + onConsumed: function(promise) { + onConsumed.observe(promise); + }, + }; + + let resolveLater = function(delay = 20) { + return new Promise((resolve, reject) => setTimeout(resolve, delay)); + }; + let rejectLater = function(delay = 20) { + return new Promise((resolve, reject) => setTimeout(reject, delay)); + }; + let makeSamples = function*() { + yield { + promise: Promise.resolve(0), + name: "Promise.resolve", + }; + yield { + promise: Promise.resolve(resolve => resolve(0)), + name: "Resolution callback", + }; + yield { + promise: Promise.resolve(0).then(null, null), + name: "`then(null, null)`" + }; + yield { + promise: Promise.reject(0).then(null, () => {}), + name: "Reject and catch immediately", + }; + yield { + promise: resolveLater(), + name: "Resolve later", + }; + yield { + promise: Promise.reject("Simple rejection"), + leftUncaught: true, + consumed: false, + name: "Promise.reject", + }; + + // Reject a promise now, consume it later. + let p = Promise.reject("Reject now, consume later"); + setTimeout(() => p.then(null, () => { + do_print("Consumed promise"); + }), 200); + yield { + promise: p, + leftUncaught: true, + consumed: true, + name: "Reject now, consume later", + }; + + yield { + promise: Promise.all([ + Promise.resolve("Promise.all"), + rejectLater() + ]), + leftUncaught: true, + name: "Rejecting through Promise.all" + }; + yield { + promise: Promise.race([ + resolveLater(500), + Promise.reject(), + ]), + leftUncaught: true, // The rejection wins the race. + name: "Rejecting through Promise.race", + }; + yield { + promise: Promise.race([ + Promise.resolve(), + rejectLater(500) + ]), + leftUncaught: false, // The resolution wins the race. + name: "Resolving through Promise.race", + }; + + let boom = new Error("`throw` in the constructor"); + yield { + promise: new Promise(() => { throw boom; }), + leftUncaught: true, + name: "Throwing in the constructor", + }; + + let rejection = Promise.reject("`reject` during resolution"); + yield { + promise: rejection, + leftUncaught: false, + consumed: false, // `rejection` is consumed immediately (see below) + name: "Promise.reject, again", + }; + + yield { + promise: new Promise(resolve => resolve(rejection)), + leftUncaught: true, + consumed: false, + name: "Resolving with a rejected promise", + }; + + yield { + promise: Promise.resolve(0).then(() => rejection), + leftUncaught: true, + consumed: false, + name: "Returning a rejected promise from success handler", + }; + + yield { + promise: Promise.resolve(0).then(() => { throw new Error(); }), + leftUncaught: true, + consumed: false, + name: "Throwing during the call to the success callback", + }; + }; + let samples = []; + for (let s of makeSamples()) { + samples.push(s); + do_print("Promise '" + s.name + "' has id " + PromiseDebugging.getPromiseID(s.promise)); + } + + PromiseDebugging.addUncaughtRejectionObserver(observer); + + for (let s of samples) { + names.set(s.promise, s.name); + if (s.leftUncaught || false) { + onLeftUncaught.expected.add(s.promise); + } + if (s.consumed || false) { + onConsumed.expected.add(s.promise); + } + } + + do_print("Test setup, waiting for callbacks."); + yield onLeftUncaught.blocker; + + do_print("All calls to onLeftUncaught are complete."); + if (onConsumed.expected.size != 0) { + do_print("onConsumed is still waiting for the following Promise:"); + do_print(JSON.stringify(Array.from(onConsumed.expected.values(), (x) => names.get(x)))); + yield onConsumed.blocker; + } + + do_print("All calls to onConsumed are complete."); + let removed = PromiseDebugging.removeUncaughtRejectionObserver(observer); + Assert.ok(removed, "removeUncaughtRejectionObserver succeeded"); + removed = PromiseDebugging.removeUncaughtRejectionObserver(observer); + Assert.ok(!removed, "second call to removeUncaughtRejectionObserver didn't remove anything"); +}); + + +add_task(function* test_uninstall_observer() { + let Observer = function() { + this.blocker = new Promise(resolve => this.resolve = resolve); + this.active = true; + }; + Observer.prototype = { + set active(x) { + this._active = x; + if (x) { + PromiseDebugging.addUncaughtRejectionObserver(this); + } else { + PromiseDebugging.removeUncaughtRejectionObserver(this); + } + }, + onLeftUncaught: function() { + Assert.ok(this._active, "This observer is active."); + this.resolve(); + }, + onConsumed: function() { + Assert.ok(false, "We should not consume any Promise."); + }, + }; + + do_print("Adding an observer."); + let deactivate = new Observer(); + Promise.reject("I am an uncaught rejection."); + yield deactivate.blocker; + Assert.ok(true, "The observer has observed an uncaught Promise."); + deactivate.active = false; + do_print("Removing the observer, it should not observe any further uncaught Promise."); + + do_print("Rejecting a Promise and waiting a little to give a chance to observers."); + let wait = new Observer(); + Promise.reject("I am another uncaught rejection."); + yield wait.blocker; + yield new Promise(resolve => setTimeout(resolve, 100)); + // Normally, `deactivate` should not be notified of the uncaught rejection. + wait.active = false; +}); + +function run_test() { + run_next_test(); +} diff --git a/dom/promise/tests/unit/xpcshell.ini b/dom/promise/tests/unit/xpcshell.ini new file mode 100644 index 000000000..73df2380b --- /dev/null +++ b/dom/promise/tests/unit/xpcshell.ini @@ -0,0 +1,5 @@ +[DEFAULT] +head = +tail = + +[test_monitor_uncaught.js] |