Skip to content

Commit

Permalink
Array.fromAsync various remaining coverage
Browse files Browse the repository at this point in the history
This contains a few more tests for Array.fromAsync, in addition to what
has already been merged and what is under review at tc39#3791.

This covers the following items from the testing plan at tc39#3725:

- Success cases
  - Creates promise
  - Create new array/arraylike in promise (with length = length property)
- Input
  - Invalid input values
    - nonconforming object (arraylike without length, missing keys)
  - Covered by polyfill tests
    - Result promise rejects if length access fails (non-iterable input)
    - Unaffected by globalThis.Symbol mutation (non-iterable)
- this-value
  - this-value is a constructor
  - this-value is not a constructor
  - If this is a constructor, and items doesn't have a Symbol.iterator,
    returns a new instance of this
  - Iterator closed when property creation on this fails
  - Returned promise rejects when ^
- Other tests
  - Error is thrown for all CreateDataProperty fails
  - Non-writable properties are overwritten by CreateDataProperty
  - Input with missing values
  • Loading branch information
ptomato committed Apr 4, 2023
1 parent ff405fd commit e30e9aa
Show file tree
Hide file tree
Showing 16 changed files with 610 additions and 0 deletions.
24 changes: 24 additions & 0 deletions test/built-ins/Array/fromAsync/asyncitems-arraylike-holes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-array.fromasync
description: Array-like object with holes treats the holes as undefined
info: |
3.k.vii.2. Let _kValue_ be ? Get(_arrayLike_, _Pk_).
features: [Array.fromAsync]
flags: [async]
includes: [asyncHelpers.js, compareArray.js]
---*/

asyncTest(async function () {
const arrayLike = Object.create(null);
arrayLike.length = 5;
arrayLike[0] = 0;
arrayLike[1] = 1;
arrayLike[2] = 2;
arrayLike[4] = 4;

const array = await Array.fromAsync(arrayLike);
assert.compareArray(array, [0, 1, 2, undefined, 4], "holes in array-like treated as undefined");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-array.fromasync
description: Rejects on array-like object whose length cannot be gotten
info: |
3.k.iii. Let _len_ be ? LengthOfArrayLike(_arrayLike_).
features: [Array.fromAsync]
flags: [async]
includes: [asyncHelpers.js]
---*/

asyncTest(async function () {
await assert.throwsAsync(Test262Error, () => Array.fromAsync({
get length() {
throw new Test262Error('accessing length property fails');
}
}), "Promise should be rejected if array-like length getter throws");

await assert.throwsAsync(TypeError, () => Array.fromAsync({
length: 1n,
0: 0
}), "Promise should be rejected if array-like length can't be converted to a number");
});
31 changes: 31 additions & 0 deletions test/built-ins/Array/fromAsync/asyncitems-arraylike-too-long.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-array.fromasync
description: >
Promise is rejected if the length of the array-like to copy is out of range
info: |
j. If _iteratorRecord_ is not *undefined*, then
...
k. Else,
...
iv. If IsConstructor(_C_) is *true*, then
...
v. Else,
1. Let _A_ be ? ArrayCreate(_len_).
ArrayCreate, step 1:
1. If _length_ > 2³² - 1, throw a *RangeError* exception.
includes: [asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/

asyncTest(async function () {
const notConstructor = {};

await assert.throwsAsync(RangeError, () => Array.fromAsync.call(notConstructor, {
length: 4294967296 // 2³²
}), "Array-like with excessive length");
});
23 changes: 23 additions & 0 deletions test/built-ins/Array/fromAsync/asyncitems-object-not-arraylike.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-array.fromasync
description: >
Treats an asyncItems object that isn't an array-like as a 0-length array-like
info: |
3.k.iii. Let _len_ be ? LengthOfArrayLike(_arrayLike_).
features: [Array.fromAsync]
flags: [async]
includes: [asyncHelpers.js, compareArray.js]
---*/

asyncTest(async function () {
const notArrayLike = Object.create(null);
notArrayLike[0] = 0;
notArrayLike[1] = 1;
notArrayLike[2] = 2;

const array = await Array.fromAsync(notArrayLike);
assert.compareArray(array, [], "non-array-like object is treated as 0-length array-like");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-array.fromasync
description: >
Use the intrinsic @@iterator and @@asyncIterator to check iterability
includes: [compareArray.js, asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/

asyncTest(async function () {
// Replace the user-reachable Symbol.iterator and Symbol.asyncIterator with
// fake symbol keys
const originalSymbol = globalThis.Symbol;
const fakeIteratorSymbol = Symbol("iterator");
const fakeAsyncIteratorSymbol = Symbol("asyncIterator");
globalThis.Symbol = {
iterator: fakeIteratorSymbol,
asyncIterator: fakeAsyncIteratorSymbol,
};

const input = {
length: 3,
0: 0,
1: 1,
2: 2,
[fakeIteratorSymbol]() {
throw new Test262Error("The fake Symbol.iterator method should not be called");
},
[fakeAsyncIteratorSymbol]() {
throw new Test262Error("The fake Symbol.asyncIterator method should not be called");
}
};
const output = await Array.fromAsync(input);
assert.compareArray(output, [0, 1, 2]);

globalThis.Symbol = originalSymbol;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-array.fromasync
description: >
Array.fromAsync returns a Promise that resolves to an Array in the normal case
info: |
1. Let _C_ be the *this* value.
...
3.e. If IsConstructor(_C_) is *true*, then
i. Let _A_ be ? Construct(_C_).
features: [Array.fromAsync]
flags: [async]
includes: [asyncHelpers.js]
---*/

asyncTest(async function () {
const promise = Array.fromAsync([0, 1, 2]);
const array = await promise;
assert(Array.isArray(array), "Array.fromAsync returns a Promise that resolves to an Array");
});
35 changes: 35 additions & 0 deletions test/built-ins/Array/fromAsync/returns-promise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-array.fromasync
description: Array.fromAsync returns a Promise
info: |
5. Return _promiseCapability_.[[Promise]].
flags: [async]
includes: [asyncHelpers.js]
features: [Array.fromAsync]
---*/

asyncTest(async function () {
let p = Array.fromAsync([0, 1, 2]);

assert(p instanceof Promise, "Array.fromAsync returns a Promise");
assert.sameValue(
Object.getPrototypeOf(p),
Promise.prototype,
"Array.fromAsync returns an object with prototype Promise.prototype"
);

p = Array.fromAsync([0, 1, 2], () => {
throw new Test262Error("this will make the Promise reject");
})
assert(p instanceof Promise, "Array.fromAsync returns a Promise even on error");
assert.sameValue(
Object.getPrototypeOf(p),
Promise.prototype,
"Array.fromAsync returns an object with prototype Promise.prototype even on error"
);

await assert.throwsAsync(Test262Error, () => p, "Prevent unhandled rejection");
});
74 changes: 74 additions & 0 deletions test/built-ins/Array/fromAsync/this-constructor-operations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-array.fromasync
description: >
Order of user-observable operations on a custom this-value and its instances
includes: [compareArray.js, asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/

function formatPropertyName(propertyKey, objectName = "") {
switch (typeof propertyKey) {
case "symbol":
if (Symbol.keyFor(propertyKey) !== undefined) {
return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
} else if (propertyKey.description.startsWith('Symbol.')) {
return `${objectName}[${propertyKey.description}]`;
} else {
return `${objectName}[Symbol('${propertyKey.description}')]`
}
case "string":
if (propertyKey !== String(Number(propertyKey)))
return objectName ? `${objectName}.${propertyKey}` : propertyKey;
// fall through
default:
// integer or string integer-index
return `${objectName}[${propertyKey}]`;
}
}

asyncTest(async function () {
const expectedCalls = [
"construct MyArray",
"defineProperty A[0]",
"defineProperty A[1]",
"set A.length"
];
const actualCalls = [];

function MyArray(...args) {
actualCalls.push("construct MyArray");
return new Proxy(Object.create(null), {
set(target, key, value) {
actualCalls.push(`set ${formatPropertyName(key, "A")}`);
return Reflect.set(target, key, value);
},
defineProperty(target, key, descriptor) {
actualCalls.push(`defineProperty ${formatPropertyName(key, "A")}`);
return Reflect.defineProperty(target, key, descriptor);
}
});
}

let result = await Array.fromAsync.call(MyArray, [1, 2]);
assert.compareArray(expectedCalls, actualCalls, "order of operations for array argument");

actualCalls.splice(0); // reset

const expectedCallsForArrayLike = [
"construct MyArray",
"construct MyArray",
"defineProperty A[0]",
"defineProperty A[1]",
"set A.length"
];
result = await Array.fromAsync.call(MyArray, {
length: 2,
0: 1,
1: 2
});
assert.compareArray(expectedCallsForArrayLike, actualCalls, "order of operations for array-like argument");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-array.fromasync
description: >
Rejects the promise if setting the length fails on an instance of a custom
this-value
info: |
3.j.ii.4.a. Perform ? Set(_A_, *"length"*, 𝔽(_k_), *true*).
...
3.k.viii. Perform ? Set(_A_, *"length"*, 𝔽(_len_), *true*)
includes: [asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/

asyncTest(async function () {
class MyArray {
set length(v) {
throw new Test262Error("setter of length property throws")
}
}

await assert.throwsAsync(Test262Error, () => Array.fromAsync.call(MyArray, [0, 1, 2]), "Promise rejected if setting length fails");

await assert.throwsAsync(Test262Error, () => Array.fromAsync.call(MyArray, {
length: 3,
0: 0,
1: 1,
2: 2
}), "Promise rejected if setting length from array-like fails");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-array.fromasync
description: >
Overwrites non-writable element properties on an instance of a custom
this-value
info: |
3.j.ii.8. Let _defineStatus_ be CreateDataPropertyOrThrow(_A_, _Pk_, _mappedValue_).
...
3.k.vii.6. Perform ? CreateDataPropertyOrThrow(_A_, _Pk_, _mappedValue_).
includes: [asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/

asyncTest(async function () {
function MyArray() {
this.length = 4;
for (let ix = 0; ix < this.length; ix++) {
Object.defineProperty(this, ix, {
enumerable: true,
writable: false,
configurable: true,
value: 99
});
}
}

let result = await Array.fromAsync.call(MyArray, [0, 1, 2]);
assert.sameValue(result.length, 3, "Length property is overwritten");
assert.sameValue(result[0], 0, "Read-only element 0 is overwritten");
assert.sameValue(result[1], 1, "Read-only element 1 is overwritten");
assert.sameValue(result[2], 2, "Read-only element 2 is overwritten");
assert.sameValue(result[3], 99, "Element 3 is not overwritten");

result = await Array.fromAsync.call(MyArray, {
length: 3,
0: 0,
1: 1,
2: 2,
3: 3 // ignored
});
assert.sameValue(result.length, 3, "Length property is overwritten");
assert.sameValue(result[0], 0, "Read-only element 0 is overwritten");
assert.sameValue(result[1], 1, "Read-only element 1 is overwritten");
assert.sameValue(result[2], 2, "Read-only element 2 is overwritten");
assert.sameValue(result[3], 99, "Element 3 is not overwritten");
});
Loading

0 comments on commit e30e9aa

Please sign in to comment.