opencode/packages/codemode/test/promise-test262.test.ts

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])
})
})