diff options
Diffstat (limited to 'js/src/tests/ecma_2017')
-rw-r--r-- | js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js | 79 | ||||
-rw-r--r-- | js/src/tests/ecma_2017/AsyncFunctions/subclass.js | 31 | ||||
-rw-r--r-- | js/src/tests/ecma_2017/lastIndex-exec.js | 80 | ||||
-rw-r--r-- | js/src/tests/ecma_2017/lastIndex-match-or-replace.js | 122 | ||||
-rw-r--r-- | js/src/tests/ecma_2017/lastIndex-search.js | 118 |
5 files changed, 430 insertions, 0 deletions
diff --git a/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js b/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js new file mode 100644 index 000000000..7d75d0c93 --- /dev/null +++ b/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js @@ -0,0 +1,79 @@ +/* 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 AsyncFunction = async function(){}.constructor; + + +// Test subclassing %AsyncFunction% works correctly. +class MyAsync extends AsyncFunction {} + +var fn = new MyAsync(); +assertEq(fn instanceof MyAsync, true); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +fn = Reflect.construct(MyAsync, []); +assertEq(fn instanceof MyAsync, true); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +fn = Reflect.construct(MyAsync, [], MyAsync); +assertEq(fn instanceof MyAsync, true); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +fn = Reflect.construct(MyAsync, [], AsyncFunction); +assertEq(fn instanceof MyAsync, false); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + + +// Set a different constructor as NewTarget. +fn = Reflect.construct(MyAsync, [], Array); +assertEq(fn instanceof MyAsync, false); +assertEq(fn instanceof AsyncFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + +fn = Reflect.construct(AsyncFunction, [], Array); +assertEq(fn instanceof AsyncFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + + +// The prototype defaults to %AsyncFunctionPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +fn = Reflect.construct(AsyncFunction, [], NewTargetNullPrototype); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + +fn = Reflect.construct(MyAsync, [], NewTargetNullPrototype); +assertEq(fn instanceof MyAsync, false); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(AsyncFunction, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +fn = Reflect.construct(AsyncFunction, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_2017/AsyncFunctions/subclass.js b/js/src/tests/ecma_2017/AsyncFunctions/subclass.js new file mode 100644 index 000000000..da20ab19b --- /dev/null +++ b/js/src/tests/ecma_2017/AsyncFunctions/subclass.js @@ -0,0 +1,31 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue +/* 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 AsyncFunction = async function(){}.constructor; + +class MyAsync extends AsyncFunction {} + +// MyGen inherits from %AsyncFunction%. +assertEq(Object.getPrototypeOf(MyAsync), AsyncFunction); + +// MyGen.prototype inherits from %AsyncFunctionPrototype%. +assertEq(Object.getPrototypeOf(MyAsync.prototype), AsyncFunction.prototype); + +var fn = new MyAsync("return await 'ok';"); + +// fn inherits from MyAsync.prototype. +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +// Ensure the new async function can be executed. +var promise = fn(); + +// promise inherits from %Promise.prototype%. +assertEq(Object.getPrototypeOf(promise), Promise.prototype); + +// Computes the expected result. +assertEventuallyEq(promise, "ok"); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_2017/lastIndex-exec.js b/js/src/tests/ecma_2017/lastIndex-exec.js new file mode 100644 index 000000000..f42facbe3 --- /dev/null +++ b/js/src/tests/ecma_2017/lastIndex-exec.js @@ -0,0 +1,80 @@ +// RegExp.prototype.exec: Test lastIndex changes for ES2017. + +// Test various combinations of: +// - Pattern matches or doesn't match +// - Global and/or sticky flag is set. +// - lastIndex exceeds the input string length +// - lastIndex is +-0 +const testCases = [ + { regExp: /a/, lastIndex: 0, input: "a", result: 0 }, + { regExp: /a/g, lastIndex: 0, input: "a", result: 1 }, + { regExp: /a/y, lastIndex: 0, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: 0, input: "b", result: 0 }, + { regExp: /a/g, lastIndex: 0, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: 0, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: -0, input: "a", result: -0 }, + { regExp: /a/g, lastIndex: -0, input: "a", result: 1 }, + { regExp: /a/y, lastIndex: -0, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: -0, input: "b", result: -0 }, + { regExp: /a/g, lastIndex: -0, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: -0, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: -1, input: "a", result: -1 }, + { regExp: /a/g, lastIndex: -1, input: "a", result: 1 }, + { regExp: /a/y, lastIndex: -1, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: -1, input: "b", result: -1 }, + { regExp: /a/g, lastIndex: -1, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: -1, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: 100, input: "a", result: 100 }, + { regExp: /a/g, lastIndex: 100, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: 100, input: "a", result: 0 }, +]; + +// Basic test. +for (let {regExp, lastIndex, input, result} of testCases) { + let re = new RegExp(regExp); + re.lastIndex = lastIndex; + re.exec(input); + assertEq(re.lastIndex, result); +} + +// Test when lastIndex is non-writable. +for (let {regExp, lastIndex, input} of testCases) { + let re = new RegExp(regExp); + Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false }); + if (re.global || re.sticky) { + assertThrowsInstanceOf(() => re.exec(input), TypeError); + } else { + re.exec(input); + } + assertEq(re.lastIndex, lastIndex); +} + +// Test when lastIndex is changed to non-writable as a side-effect. +for (let {regExp, lastIndex, input} of testCases) { + let re = new RegExp(regExp); + let called = false; + re.lastIndex = { + valueOf() { + assertEq(called, false); + called = true; + Object.defineProperty(re, "lastIndex", { value: 9000, writable: false }); + return lastIndex; + } + }; + if (re.global || re.sticky) { + assertThrowsInstanceOf(() => re.exec(input), TypeError); + } else { + re.exec(input); + } + assertEq(re.lastIndex, 9000); + assertEq(called, true); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_2017/lastIndex-match-or-replace.js b/js/src/tests/ecma_2017/lastIndex-match-or-replace.js new file mode 100644 index 000000000..b0a8b537c --- /dev/null +++ b/js/src/tests/ecma_2017/lastIndex-match-or-replace.js @@ -0,0 +1,122 @@ +// RegExp.prototype[Symbol.match, Symbol.replace]: Test lastIndex changes for ES2017. + +// RegExp-like class to test the RegExp method slow paths. +class DuckRegExp extends RegExp { + constructor(pattern, flags) { + return Object.create(DuckRegExp.prototype, { + regExp: { + value: new RegExp(pattern, flags) + }, + lastIndex: { + value: 0, writable: true, enumerable: false, configurable: false + } + }); + } + + exec(...args) { + this.regExp.lastIndex = this.lastIndex; + try { + return this.regExp.exec(...args); + } finally { + if (this.global || this.sticky) + this.lastIndex = this.regExp.lastIndex; + } + } + + get source() { return this.regExp.source; } + + get global() { return this.regExp.global; } + get ignoreCase() { return this.regExp.ignoreCase; } + get multiline() { return this.regExp.multiline; } + get sticky() { return this.regExp.sticky; } + get unicode() { return this.regExp.unicode; } +} + +// Test various combinations of: +// - Pattern matches or doesn't match +// - Global and/or sticky flag is set. +// - lastIndex exceeds the input string length +// - lastIndex is +-0 +const testCases = [ + { regExp: /a/, lastIndex: 0, input: "a", result: 0 }, + { regExp: /a/g, lastIndex: 0, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: 0, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: 0, input: "b", result: 0 }, + { regExp: /a/g, lastIndex: 0, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: 0, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: -0, input: "a", result: -0 }, + { regExp: /a/g, lastIndex: -0, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: -0, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: -0, input: "b", result: -0 }, + { regExp: /a/g, lastIndex: -0, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: -0, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: -1, input: "a", result: -1 }, + { regExp: /a/g, lastIndex: -1, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: -1, input: "a", result: 1 }, + + { regExp: /a/, lastIndex: -1, input: "b", result: -1 }, + { regExp: /a/g, lastIndex: -1, input: "b", result: 0 }, + { regExp: /a/y, lastIndex: -1, input: "b", result: 0 }, + + { regExp: /a/, lastIndex: 100, input: "a", result: 100 }, + { regExp: /a/g, lastIndex: 100, input: "a", result: 0 }, + { regExp: /a/y, lastIndex: 100, input: "a", result: 0 }, +]; + +for (let method of [RegExp.prototype[Symbol.match], RegExp.prototype[Symbol.replace]]) { + for (let Constructor of [RegExp, DuckRegExp]) { + // Basic test. + for (let {regExp, lastIndex, input, result} of testCases) { + let re = new Constructor(regExp); + re.lastIndex = lastIndex; + Reflect.apply(method, re, [input]); + assertEq(re.lastIndex, result); + } + + // Test when lastIndex is non-writable. + for (let {regExp, lastIndex, input} of testCases) { + let re = new Constructor(regExp); + Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false }); + if (re.global || re.sticky) { + assertThrowsInstanceOf(() => Reflect.apply(method, re, [input]), TypeError); + } else { + Reflect.apply(method, re, [input]); + } + assertEq(re.lastIndex, lastIndex); + } + + // Test when lastIndex is changed to non-writable as a side-effect. + for (let {regExp, lastIndex, input, result} of testCases) { + let re = new Constructor(regExp); + let called = false; + re.lastIndex = { + valueOf() { + assertEq(called, false); + called = true; + Object.defineProperty(re, "lastIndex", { value: 9000, writable: false }); + return lastIndex; + } + }; + if (re.sticky) { + assertThrowsInstanceOf(() => Reflect.apply(method, re, [input]), TypeError); + assertEq(called, true); + assertEq(re.lastIndex, 9000); + } else if (re.global) { + Reflect.apply(method, re, [input]); + assertEq(called, false); + assertEq(re.lastIndex, result); + } else { + Reflect.apply(method, re, [input]); + assertEq(called, true); + assertEq(re.lastIndex, 9000); + } + } + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_2017/lastIndex-search.js b/js/src/tests/ecma_2017/lastIndex-search.js new file mode 100644 index 000000000..5953b3a88 --- /dev/null +++ b/js/src/tests/ecma_2017/lastIndex-search.js @@ -0,0 +1,118 @@ +// RegExp.prototype[Symbol.search]: Test lastIndex changes for ES2017. + +// RegExp-like class to test the RegExp method slow paths. +class DuckRegExp extends RegExp { + constructor(pattern, flags) { + return Object.create(DuckRegExp.prototype, { + regExp: { + value: new RegExp(pattern, flags) + }, + lastIndex: { + value: 0, writable: true, enumerable: false, configurable: false + } + }); + } + + exec(...args) { + this.regExp.lastIndex = this.lastIndex; + try { + return this.regExp.exec(...args); + } finally { + if (this.global || this.sticky) + this.lastIndex = this.regExp.lastIndex; + } + } + + get source() { return this.regExp.source; } + + get global() { return this.regExp.global; } + get ignoreCase() { return this.regExp.ignoreCase; } + get multiline() { return this.regExp.multiline; } + get sticky() { return this.regExp.sticky; } + get unicode() { return this.regExp.unicode; } +} + +// Test various combinations of: +// - Pattern matches or doesn't match +// - Global and/or sticky flag is set. +// - lastIndex exceeds the input string length +// - lastIndex is +-0 +const testCasesNotPositiveZero = [ + { regExp: /a/, lastIndex: -1, input: "a" }, + { regExp: /a/g, lastIndex: -1, input: "a" }, + { regExp: /a/y, lastIndex: -1, input: "a" }, + + { regExp: /a/, lastIndex: 100, input: "a" }, + { regExp: /a/g, lastIndex: 100, input: "a" }, + { regExp: /a/y, lastIndex: 100, input: "a" }, + + { regExp: /a/, lastIndex: -1, input: "b" }, + { regExp: /a/g, lastIndex: -1, input: "b" }, + { regExp: /a/y, lastIndex: -1, input: "b" }, + + { regExp: /a/, lastIndex: -0, input: "a" }, + { regExp: /a/g, lastIndex: -0, input: "a" }, + { regExp: /a/y, lastIndex: -0, input: "a" }, + + { regExp: /a/, lastIndex: -0, input: "b" }, + { regExp: /a/g, lastIndex: -0, input: "b" }, + { regExp: /a/y, lastIndex: -0, input: "b" }, +]; + +const testCasesPositiveZero = [ + { regExp: /a/, lastIndex: 0, input: "a" }, + { regExp: /a/g, lastIndex: 0, input: "a" }, + { regExp: /a/y, lastIndex: 0, input: "a" }, + + { regExp: /a/, lastIndex: 0, input: "b" }, + { regExp: /a/g, lastIndex: 0, input: "b" }, + { regExp: /a/y, lastIndex: 0, input: "b" }, +]; + +const testCases = [...testCasesNotPositiveZero, ...testCasesPositiveZero]; + +for (let Constructor of [RegExp, DuckRegExp]) { + // Basic test. + for (let {regExp, lastIndex, input} of testCases) { + let re = new Constructor(regExp); + re.lastIndex = lastIndex; + re[Symbol.search](input); + assertEq(re.lastIndex, lastIndex); + } + + // Test when lastIndex is non-writable and not positive zero. + for (let {regExp, lastIndex, input} of testCasesNotPositiveZero) { + let re = new Constructor(regExp); + Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false }); + assertThrowsInstanceOf(() => re[Symbol.search](input), TypeError); + assertEq(re.lastIndex, lastIndex); + } + + // Test when lastIndex is non-writable and positive zero. + for (let {regExp, lastIndex, input} of testCasesPositiveZero) { + let re = new Constructor(regExp); + Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false }); + if (re.global || re.sticky) { + assertThrowsInstanceOf(() => re[Symbol.search](input), TypeError); + } else { + re[Symbol.search](input); + } + assertEq(re.lastIndex, lastIndex); + } + + // Test lastIndex isn't converted to a number. + for (let {regExp, lastIndex, input} of testCases) { + let re = new RegExp(regExp); + let badIndex = { + valueOf() { + assertEq(false, true); + } + }; + re.lastIndex = badIndex; + re[Symbol.search](input); + assertEq(re.lastIndex, badIndex); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); |