334 lines
12 KiB
TypeScript
334 lines
12 KiB
TypeScript
/*
|
|
* Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
|
|
* - test/built-ins/Promise/all/S25.4.4.1_A1.1_T1.js
|
|
* - test/built-ins/Promise/all/S25.4.4.1_A2.1_T1.js
|
|
* - test/built-ins/Promise/all/S25.4.4.1_A2.3_T1.js
|
|
* - test/built-ins/Promise/all/S25.4.4.1_A2.3_T2.js
|
|
* - test/built-ins/Promise/all/S25.4.4.1_A2.3_T3.js
|
|
* - test/built-ins/Promise/all/S25.4.4.1_A7.1_T1.js
|
|
* - test/built-ins/Promise/all/S25.4.4.1_A8.2_T1.js
|
|
* - test/built-ins/Promise/all/S25.4.4.1_A8.2_T2.js
|
|
* - test/built-ins/Promise/all/iter-arg-is-number-reject.js
|
|
* - test/built-ins/Promise/all/resolve-non-thenable.js
|
|
* - test/built-ins/Promise/allSettled/is-function.js
|
|
* - test/built-ins/Promise/allSettled/returns-promise.js
|
|
* - test/built-ins/Promise/allSettled/resolves-empty-array.js
|
|
* - test/built-ins/Promise/allSettled/resolves-to-array.js
|
|
* - test/built-ins/Promise/allSettled/resolved-all-fulfilled.js
|
|
* - test/built-ins/Promise/allSettled/resolved-all-mixed.js
|
|
* - test/built-ins/Promise/allSettled/resolved-all-rejected.js
|
|
* - test/built-ins/Promise/allSettled/iter-arg-is-number-reject.js
|
|
* - test/built-ins/Promise/allSettled/resolve-non-thenable.js
|
|
* - test/built-ins/Promise/race/S25.4.4.3_A1.1_T1.js
|
|
* - test/built-ins/Promise/race/S25.4.4.3_A7.1_T1.js
|
|
* - test/built-ins/Promise/race/S25.4.4.3_A7.1_T2.js
|
|
* - test/built-ins/Promise/race/S25.4.4.3_A7.1_T3.js
|
|
* - test/built-ins/Promise/race/S25.4.4.3_A7.2_T1.js
|
|
* - test/built-ins/Promise/race/S25.4.4.3_A7.3_T1.js
|
|
* - test/built-ins/Promise/race/S25.4.4.3_A7.3_T2.js
|
|
* - test/built-ins/Promise/race/iter-arg-is-number-reject.js
|
|
* - test/built-ins/Promise/resolve/S25.4.4.5_A1.1_T1.js
|
|
* - test/built-ins/Promise/resolve/S25.4.4.5_A2.1_T1.js
|
|
* - test/built-ins/Promise/resolve/resolve-non-obj.js
|
|
* - test/built-ins/Promise/resolve/resolve-non-thenable.js
|
|
* - test/built-ins/Promise/reject/S25.4.4.4_A1.1_T1.js
|
|
* - test/built-ins/Promise/reject/S25.4.4.4_A2.1_T1.js
|
|
* - test/built-ins/Array/from/from-array.js
|
|
* - test/language/statements/async-function/declaration-returns-promise.js
|
|
* - test/language/statements/async-function/evaluation-body-that-returns.js
|
|
* - test/language/statements/async-function/evaluation-body-that-returns-after-await.js
|
|
* - test/language/statements/async-function/evaluation-body-that-throws.js
|
|
* - test/language/statements/async-function/evaluation-body-that-throws-after-await.js
|
|
*
|
|
* Copyright 2014 Cubane Canada, Inc. All rights reserved.
|
|
* Copyright (C) 2016 the V8 project authors. All rights reserved.
|
|
* Copyright (C) 2019 Leo Balter. All rights reserved.
|
|
* Copyright (C) 2018 Rick Waldron. All rights reserved.
|
|
* Copyright 2015 Microsoft Corporation. All rights reserved.
|
|
* Copyright 2016 Microsoft, Inc. All rights reserved.
|
|
* Test262 portions are governed by the BSD license in LICENSE.test262.
|
|
*/
|
|
import { describe, expect, test } from "bun:test"
|
|
import { Effect } from "effect"
|
|
import { CodeMode } from "../src/index.js"
|
|
|
|
const value = async (code: string) => {
|
|
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
|
|
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
|
|
return result.value
|
|
}
|
|
|
|
describe("Test262 Promise static adaptations", () => {
|
|
test("Promise statics are callable", async () => {
|
|
expect(
|
|
await value(
|
|
`return [typeof Promise.all, typeof Promise.allSettled, typeof Promise.race, typeof Promise.resolve, typeof Promise.reject]`,
|
|
),
|
|
).toEqual(["function", "function", "function", "function", "function"])
|
|
})
|
|
|
|
test("invalid combinator inputs return rejected TypeError promises", async () => {
|
|
expect(
|
|
await value(`
|
|
const observe = async (run) => {
|
|
let returned = false
|
|
try {
|
|
const promise = run()
|
|
returned = promise instanceof Promise
|
|
await promise
|
|
return [returned, "fulfilled"]
|
|
} catch (error) {
|
|
return [returned, error.name]
|
|
}
|
|
}
|
|
return await Promise.all([
|
|
observe(() => Promise.all(42)),
|
|
observe(() => Promise.allSettled(42)),
|
|
observe(() => Promise.race(42)),
|
|
])
|
|
`),
|
|
).toEqual([
|
|
[true, "TypeError"],
|
|
[true, "TypeError"],
|
|
[true, "TypeError"],
|
|
])
|
|
})
|
|
|
|
test("Promise.all returns a promise and creates a fresh empty array", async () => {
|
|
expect(
|
|
await value(`
|
|
const input = []
|
|
const promise = Promise.all(input)
|
|
const result = await promise
|
|
return [promise instanceof Promise, result instanceof Array, result.length, result !== input]
|
|
`),
|
|
).toEqual([true, true, 0, true])
|
|
})
|
|
|
|
test("Promise.all preserves values and input order", async () => {
|
|
expect(
|
|
await value(`
|
|
const first = { id: 1 }
|
|
const second = { id: 2 }
|
|
const result = await Promise.all([Promise.resolve(3), first, Promise.resolve(second)])
|
|
return [result.length, result[0], result[1] === first, result[2] === second]
|
|
`),
|
|
).toEqual([3, 3, true, true])
|
|
})
|
|
|
|
test("Promise.all adopts rejection from either input position", async () => {
|
|
expect(
|
|
await value(`
|
|
const observe = async (promise) => {
|
|
try { await promise; return "fulfilled" } catch (reason) { return reason }
|
|
}
|
|
return await Promise.all([
|
|
observe(Promise.all([Promise.reject(1), Promise.resolve(2)])),
|
|
observe(Promise.all([Promise.resolve(1), Promise.reject(2)])),
|
|
])
|
|
`),
|
|
).toEqual([1, 2])
|
|
})
|
|
|
|
test("Promise.all treats sparse positions as undefined values", async () => {
|
|
expect(
|
|
await value(`
|
|
const input = []
|
|
input[1] = 1
|
|
const result = await Promise.all(input)
|
|
return [result.length, result[0] === undefined, result[1]]
|
|
`),
|
|
).toEqual([2, true, 1])
|
|
})
|
|
|
|
test("Promise.allSettled returns a promise and a fresh array", async () => {
|
|
expect(
|
|
await value(`
|
|
const input = []
|
|
const promise = Promise.allSettled(input)
|
|
const result = await promise
|
|
return [promise instanceof Promise, result instanceof Array, result.length, result !== input]
|
|
`),
|
|
).toEqual([true, true, 0, true])
|
|
})
|
|
|
|
test("Promise.allSettled preserves order and settlement shapes", async () => {
|
|
expect(
|
|
await value(`
|
|
const reason = { id: 4 }
|
|
const result = await Promise.allSettled([
|
|
Promise.resolve(1),
|
|
Promise.reject(2),
|
|
3,
|
|
Promise.reject(reason),
|
|
])
|
|
return [result, result.map((item) => Object.keys(item))]
|
|
`),
|
|
).toEqual([
|
|
[
|
|
{ status: "fulfilled", value: 1 },
|
|
{ status: "rejected", reason: 2 },
|
|
{ status: "fulfilled", value: 3 },
|
|
{ status: "rejected", reason: { id: 4 } },
|
|
],
|
|
[
|
|
["status", "value"],
|
|
["status", "reason"],
|
|
["status", "value"],
|
|
["status", "reason"],
|
|
],
|
|
])
|
|
})
|
|
|
|
test("Promise.allSettled preserves input order across settlement timing", async () => {
|
|
expect(
|
|
await value(`
|
|
const slow = async () => { await Promise.resolve(); return "slow" }
|
|
return await Promise.allSettled([slow(), Promise.resolve("fast")])
|
|
`),
|
|
).toEqual([
|
|
{ status: "fulfilled", value: "slow" },
|
|
{ status: "fulfilled", value: "fast" },
|
|
])
|
|
})
|
|
|
|
test("Promise.allSettled adopts a non-thenable object", async () => {
|
|
expect(
|
|
await value(`
|
|
const object = { id: 1 }
|
|
const result = await Promise.allSettled([object])
|
|
return [result, result[0].value === object]
|
|
`),
|
|
).toEqual([[{ status: "fulfilled", value: { id: 1 } }], true])
|
|
})
|
|
|
|
test("Promise.allSettled treats sparse positions as undefined values", async () => {
|
|
expect(
|
|
await value(`
|
|
const input = []
|
|
input[1] = 1
|
|
const result = await Promise.allSettled(input)
|
|
return [result.length, result[0].status, result[0].value === undefined, result[1]]
|
|
`),
|
|
).toEqual([2, "fulfilled", true, { status: "fulfilled", value: 1 }])
|
|
})
|
|
|
|
test("Promise.race returns a promise and settles from its first input", async () => {
|
|
expect(
|
|
await value(`
|
|
const rejected = Promise.reject(2)
|
|
const fulfilled = Promise.resolve(1)
|
|
const promise = Promise.race([fulfilled, rejected])
|
|
return [promise instanceof Promise, await promise]
|
|
`),
|
|
).toEqual([true, 1])
|
|
})
|
|
|
|
test("Promise.race preserves primitive fulfillment and rejection", async () => {
|
|
expect(
|
|
await value(`
|
|
const fulfilled = await Promise.race([23])
|
|
let rejected
|
|
try { await Promise.race([Promise.reject(7)]) } catch (reason) { rejected = reason }
|
|
return [fulfilled, rejected]
|
|
`),
|
|
).toEqual([23, 7])
|
|
})
|
|
|
|
test("Promise.race observes later fulfillment and rejection", async () => {
|
|
expect(
|
|
await value(`
|
|
const slow = async () => { await Promise.resolve(); await Promise.resolve(); return 1 }
|
|
const rejectSoon = async () => { await Promise.resolve(); throw 2 }
|
|
try { await Promise.race([slow(), rejectSoon()]); return "fulfilled" } catch (reason) { return reason }
|
|
`),
|
|
).toBe(2)
|
|
})
|
|
|
|
test("Promise.race selects fulfilled and rejected inputs by settlement order", async () => {
|
|
expect(
|
|
await value(`
|
|
const delayed = async (value) => { await Promise.resolve(); return value }
|
|
const first = await Promise.race([Promise.resolve(1), Promise.resolve(2)])
|
|
const second = await Promise.race([Promise.resolve(1), delayed(9)])
|
|
const third = await Promise.race([delayed(9), Promise.resolve(2)])
|
|
let fourth
|
|
try { await Promise.race([Promise.reject(1), Promise.resolve(2)]) } catch (reason) { fourth = reason }
|
|
return [first, second, third, fourth]
|
|
`),
|
|
).toEqual([1, 1, 2, 1])
|
|
})
|
|
|
|
test("Promise.race treats a sparse first position as undefined", async () => {
|
|
expect(
|
|
await value(`
|
|
const input = []
|
|
input[1] = 1
|
|
return (await Promise.race(input)) === undefined
|
|
`),
|
|
).toBe(true)
|
|
})
|
|
|
|
test("Promise.resolve adopts values and nested promises", async () => {
|
|
expect(
|
|
await value(`
|
|
const object = { id: 1 }
|
|
return [
|
|
await Promise.resolve(42),
|
|
await Promise.resolve(Promise.resolve("nested")),
|
|
(await Promise.resolve(object)) === object,
|
|
]
|
|
`),
|
|
).toEqual([42, "nested", true])
|
|
})
|
|
|
|
test("Promise.resolve preserves promise identity", async () => {
|
|
expect(
|
|
await value(`
|
|
const promise = Promise.resolve(1)
|
|
const identities = new Map([[promise, "same"]])
|
|
return identities.get(Promise.resolve(promise))
|
|
`),
|
|
).toBe("same")
|
|
})
|
|
|
|
test("Promise.reject preserves primitive and object reasons", async () => {
|
|
expect(
|
|
await value(`
|
|
const object = { reason: true }
|
|
const reasons = [undefined, null, false, true, 0, "", 42, object]
|
|
const observe = async (reason) => {
|
|
try { await Promise.reject(reason); return false } catch (caught) { return caught === reason }
|
|
}
|
|
return await Promise.all(reasons.map(observe))
|
|
`),
|
|
).toEqual([true, true, true, true, true, true, true, true])
|
|
})
|
|
})
|
|
|
|
describe("Test262 async function adaptations", () => {
|
|
test("async functions return promises and adopt returned values", async () => {
|
|
expect(
|
|
await value(`
|
|
const plain = async () => 42
|
|
const afterAwait = async () => { await Promise.resolve(); return 43 }
|
|
const first = plain()
|
|
const second = afterAwait()
|
|
return [first instanceof Promise, second instanceof Promise, await first, await second]
|
|
`),
|
|
).toEqual([true, true, 42, 43])
|
|
})
|
|
|
|
test("async functions reject for throws before and after await", async () => {
|
|
expect(
|
|
await value(`
|
|
const throwsBefore = async () => { throw 1 }
|
|
const throwsAfter = async () => { await Promise.resolve(); throw 2 }
|
|
const observe = async (promise) => {
|
|
try { await promise; return "fulfilled" } catch (reason) { return reason }
|
|
}
|
|
return await Promise.all([observe(throwsBefore()), observe(throwsAfter())])
|
|
`),
|
|
).toEqual([1, 2])
|
|
})
|
|
})
|