fix(codemode): align string, array, and Date behavior (#37775)
This commit is contained in:
parent
fe0c74f4df
commit
391cfbcfe7
6 changed files with 165 additions and 36 deletions
|
|
@ -131,10 +131,6 @@ describe("constructors callable without new, like JS", () => {
|
|||
expect((await error(`try { Array(-1) } catch (e) { throw Error(e.name) }`)).message).toContain("RangeError")
|
||||
})
|
||||
|
||||
test("sort densifies trailing holes into undefined (documented divergence)", async () => {
|
||||
expect(await value(`return Array(2).sort().map(() => 1)`)).toEqual([1, 1])
|
||||
})
|
||||
|
||||
test("returned sparse arrays normalize holes to null at the host boundary", async () => {
|
||||
expect(await value(`return Array(3)`)).toEqual([null, null, null])
|
||||
})
|
||||
|
|
@ -153,6 +149,57 @@ describe("constructors callable without new, like JS", () => {
|
|||
})
|
||||
|
||||
describe("sort accepts the unified callback set", () => {
|
||||
test("sort preserves trailing holes while toSorted densifies them", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const defaultSorted = [2, , 1]
|
||||
const compared = [2, , 1]
|
||||
const copied = defaultSorted.toSorted()
|
||||
defaultSorted.sort()
|
||||
compared.sort((a, b) => a - b)
|
||||
return [
|
||||
Object.hasOwn(defaultSorted, 2),
|
||||
Object.hasOwn(compared, 2),
|
||||
Object.hasOwn(copied, 2),
|
||||
]
|
||||
`),
|
||||
).toEqual([false, false, true])
|
||||
|
||||
expect(await value(`const values = [2, undefined, 1]; values.sort(); return Object.hasOwn(values, 2)`)).toBe(true)
|
||||
})
|
||||
|
||||
test("sort writes its snapshot without discarding comparator length mutations", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const values = [3, 2, 1]
|
||||
let first = true
|
||||
values.sort((a, b) => {
|
||||
if (first) {
|
||||
first = false
|
||||
values.push("kept")
|
||||
}
|
||||
return a - b
|
||||
})
|
||||
return values
|
||||
`),
|
||||
).toEqual([1, 2, 3, "kept"])
|
||||
|
||||
expect(
|
||||
await value(`
|
||||
const values = [3, , 1, , 2]
|
||||
let first = true
|
||||
values.sort((a, b) => {
|
||||
if (first) {
|
||||
first = false
|
||||
values.splice(0)
|
||||
}
|
||||
return a - b
|
||||
})
|
||||
return { values, owns: values.map((_, index) => Object.hasOwn(values, index)) }
|
||||
`),
|
||||
).toEqual({ values: [1, 2, 3], owns: [true, true, true] })
|
||||
})
|
||||
|
||||
test("sort and toSorted take built-in comparators", async () => {
|
||||
expect(await value(`return [0, 1, 0].sort(Boolean)`)).toEqual([0, 0, 1])
|
||||
expect(await value(`return [0, 1, 0].toSorted(Boolean)`)).toEqual([0, 0, 1])
|
||||
|
|
|
|||
|
|
@ -1,3 +1,14 @@
|
|||
/*
|
||||
* Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
|
||||
* - test/built-ins/Date/value-to-primitive-result-non-string-prim.js
|
||||
* - test/built-ins/Date/value-to-primitive-result-string.js
|
||||
*
|
||||
* CodeMode does not support Symbol.toPrimitive, so these cases exercise the same Date-constructor primitive-result
|
||||
* handling through supported own valueOf and toString functions.
|
||||
*
|
||||
* Copyright (C) 2016 the V8 project authors. All rights reserved.
|
||||
* Test262 portions are governed by the BSD license in LICENSE.test262.
|
||||
*/
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { CodeMode, Tool } from "../src/index.js"
|
||||
|
|
@ -55,6 +66,52 @@ describe("Date", () => {
|
|||
expect(await value(`return Date.parse("2024-01-02T03:04:05.000Z")`)).toBe(1704164645000)
|
||||
})
|
||||
|
||||
test("one-argument construction coerces supported values like JavaScript", async () => {
|
||||
expect(
|
||||
await value(`return [new Date(true).getTime(), new Date(false).getTime(), new Date(null).getTime()]`),
|
||||
).toEqual([1, 0, 0])
|
||||
expect(await value(`return Number.isNaN(new Date(undefined).getTime())`)).toBe(true)
|
||||
expect(await value(`return Number.isNaN(new Date([]).getTime())`)).toBe(true)
|
||||
expect(await value(`return new Date(["1970-01-01T00:00:00.000Z"]).getTime()`)).toBe(0)
|
||||
expect(await value(`return Number.isNaN(new Date({}).getTime())`)).toBe(true)
|
||||
})
|
||||
|
||||
test("one-argument construction uses valueOf then toString for objects", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const calls = []
|
||||
const number = { valueOf: () => 8 }
|
||||
const text = {
|
||||
valueOf: () => { calls.push("valueOf"); return {} },
|
||||
toString: () => { calls.push("toString"); return "2016-06-05T18:40:00.000Z" },
|
||||
}
|
||||
return [new Date(number).getTime(), new Date(text).getTime(), calls]
|
||||
`),
|
||||
).toEqual([8, 1465152000000, ["valueOf", "toString"]])
|
||||
|
||||
expect(
|
||||
await value(`
|
||||
const values = [
|
||||
{ valueOf: () => undefined },
|
||||
{ valueOf: () => true },
|
||||
{ valueOf: () => false },
|
||||
{ valueOf: () => null },
|
||||
]
|
||||
return values.map((item) => new Date(item).getTime())
|
||||
`),
|
||||
).toEqual([null, 1, 0, 0])
|
||||
|
||||
expect(
|
||||
await value(`
|
||||
try {
|
||||
new Date({ valueOf: () => ({}), toString: () => ({}) })
|
||||
} catch (error) {
|
||||
return error.name
|
||||
}
|
||||
`),
|
||||
).toBe("TypeError")
|
||||
})
|
||||
|
||||
test("date arithmetic and comparison use the time value", async () => {
|
||||
expect(await value(`const a = new Date(1000); const b = new Date(3000); return b - a`)).toBe(2000)
|
||||
expect(await value(`const a = new Date(1000); const b = new Date(3000); return a < b`)).toBe(true)
|
||||
|
|
@ -74,9 +131,9 @@ describe("Date", () => {
|
|||
expect(await value(`return new Date("garbage").toJSON()`)).toBeNull()
|
||||
})
|
||||
|
||||
test("toISOString on an invalid date is a catchable error", async () => {
|
||||
expect(await value(`try { new Date("garbage").toISOString(); return "no" } catch { return "caught" }`)).toBe(
|
||||
"caught",
|
||||
test("toISOString on an invalid date throws RangeError", async () => {
|
||||
expect(await value(`try { new Date("garbage").toISOString() } catch (error) { return error.name }`)).toBe(
|
||||
"RangeError",
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -198,7 +255,7 @@ describe("RegExp", () => {
|
|||
).toBe("7null[object Object]")
|
||||
})
|
||||
|
||||
test("function replacers can await effectful tool calls", async () => {
|
||||
test("promise-returning string replacers are coerced synchronously", async () => {
|
||||
const decorate = Tool.make({
|
||||
description: "Decorate a string",
|
||||
input: Schema.String,
|
||||
|
|
@ -211,7 +268,7 @@ describe("RegExp", () => {
|
|||
code: `return "a1b22".replace(/\\d+/g, async (match) => await tools.host.decorate(match))`,
|
||||
}),
|
||||
)
|
||||
expect(result.ok && result.value).toBe("a[1]b[22]")
|
||||
expect(result.ok && result.value).toBe("a[object Promise]b[object Promise]")
|
||||
|
||||
const missingAwait = await Effect.runPromise(
|
||||
CodeMode.execute({
|
||||
|
|
@ -219,8 +276,7 @@ describe("RegExp", () => {
|
|||
code: `return "a1".replace(/\\d/, (match) => tools.host.decorate(match))`,
|
||||
}),
|
||||
)
|
||||
expect(!missingAwait.ok && missingAwait.error.kind).toBe("InvalidDataValue")
|
||||
expect(!missingAwait.ok && missingAwait.error.message).toContain("un-awaited Promise")
|
||||
expect(missingAwait.ok && missingAwait.value).toBe("a[object Promise]")
|
||||
})
|
||||
|
||||
test("replaceAll without the g flag is a catchable error", async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue