/* * 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 * - test/built-ins/Date/prototype/toUTCString/format.js * - test/built-ins/Date/prototype/toUTCString/invalid-date.js * - test/built-ins/RegExp/prototype/exec/S15.10.6.2_A4_T8.js * * CodeMode does not support Symbol.toPrimitive, so the Date-constructor cases exercise the same primitive-result * handling through supported own valueOf and toString functions. * * Copyright (C) 2016 the V8 project authors. All rights reserved. * Copyright (C) 2017 the V8 project authors. All rights reserved. * Copyright 2009 the Sputnik 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" // Standard-library value types: Date, RegExp, Map, Set. Programs use them as ordinary JS; // intra-CodeMode checkpoints (Object.* helpers, spread, coercion inputs) preserve the live // values. JSON.stringify keeps Date -> ISO string (invalid -> null), URL -> href, and // RegExp/Map/Set/URLSearchParams -> {}. The host boundary matches that except URLSearchParams, // which cross as their query string, and Set, which crosses as an array. const run = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools: {} })) const value = async (code: string) => { const result = await run(code) if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`) return result.value } const error = async (code: string) => { const result = await run(code) if (result.ok) throw new Error(`expected failure, got value ${JSON.stringify(result.value)}`) return result.error } describe("Number and Math", () => { test("Math.random returns a number in [0, 1)", async () => { expect(await value(`const n = Math.random(); return typeof n === "number" && n >= 0 && n < 1`)).toBe(true) }) test("Number exposes native non-finite constants", async () => { expect( await value( `return [Number.isNaN(Number.NaN), Number.POSITIVE_INFINITY === Infinity, Number.NEGATIVE_INFINITY === -Infinity]`, ), ).toEqual([true, true, true]) }) test("Number valueOf returns its primitive receiver", async () => { expect(await value(`return (42).valueOf()`)).toBe(42) }) test("Number valueOf does not enable boxed numbers", async () => { const failure = await error(`return new Number(42)`) expect(failure.kind).toBe("ExecutionFailure") expect(failure.message).toContain("new Number(...) is not supported; call Number(...) without new instead.") }) }) describe("Date", () => { test("Date.now() returns a number", async () => { expect(await value(`return typeof Date.now()`)).toBe("number") }) test("epoch construction and ISO rendering", async () => { expect(await value(`return new Date(0).toISOString()`)).toBe("1970-01-01T00:00:00.000Z") }) test("string parsing round-trips", async () => { expect(await value(`return new Date("2024-01-02T03:04:05.000Z").getTime()`)).toBe(1704164645000) 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) expect(await value(`return +new Date(42)`)).toBe(42) }) test("UTC getters read calendar components", async () => { expect( await value( `const d = new Date("2024-03-05T06:07:08.009Z"); return [d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()]`, ), ).toEqual([2024, 2, 5, 6, 7, 8, 9]) }) test("invalid dates yield NaN times, guardable in-CodeMode", async () => { expect(await value(`return Number.isNaN(new Date("garbage").getTime())`)).toBe(true) expect(await value(`return new Date("garbage").toJSON()`)).toBeNull() }) test("toISOString on an invalid date throws RangeError", async () => { expect(await value(`try { new Date("garbage").toISOString() } catch (error) { return error.name }`)).toBe( "RangeError", ) }) test("template interpolation renders the ISO form", async () => { expect(await value("return `at ${new Date(0)}`")).toBe("at 1970-01-01T00:00:00.000Z") }) test("dates serialize to ISO strings at the boundary, direct and nested", async () => { expect(await value(`return new Date(0)`)).toBe("1970-01-01T00:00:00.000Z") expect(await value(`return { when: new Date(0), tags: [new Date(1000)] }`)).toEqual({ when: "1970-01-01T00:00:00.000Z", tags: ["1970-01-01T00:00:01.000Z"], }) expect(await value(`return JSON.stringify({ d: new Date(0) })`)).toBe('{"d":"1970-01-01T00:00:00.000Z"}') }) test("coercions: Number is the time, String is ISO, Boolean is true", async () => { expect(await value(`return Number(new Date(5))`)).toBe(5) expect(await value(`return String(new Date(0))`)).toBe("1970-01-01T00:00:00.000Z") expect(await value(`return Boolean(new Date(0))`)).toBe(true) }) test("sorting dates with a numeric comparator", async () => { expect( await value(` const dates = [new Date(3000), new Date(1000), new Date(2000)] return dates.sort((a, b) => a - b).map((d) => d.getTime()) `), ).toEqual([1000, 2000, 3000]) }) test("new Date(year, month, day) accepts component form", async () => { expect(await value(`const d = new Date(2024, 0, 2); return [d.getFullYear(), d.getMonth(), d.getDate()]`)).toEqual([ 2024, 0, 2, ]) }) test("typeof and unknown properties are forgiving", async () => { expect(await value(`return typeof new Date(0)`)).toBe("object") expect(await value(`return new Date(0).nope === undefined`)).toBe(true) }) test("toUTCString and toGMTString use the native UTC format", async () => { expect( await value(` const date = new Date(0) return [ date.toUTCString(), date.toGMTString(), new Date(NaN).toUTCString(), new Date("0020-01-01T00:00:00Z").toUTCString(), ] `), ).toEqual([ "Thu, 01 Jan 1970 00:00:00 GMT", "Thu, 01 Jan 1970 00:00:00 GMT", "Invalid Date", "Wed, 01 Jan 0020 00:00:00 GMT", ]) }) }) describe("RegExp", () => { test("literal test", async () => { expect(await value(`return /ab+c/.test("xabbbc")`)).toBe(true) expect(await value(`return /ab+c/.test("nope")`)).toBe(false) }) test("exec exposes captures and index", async () => { expect(await value(`const m = /a(b+)/.exec("xxabbc"); return { full: m[0], group: m[1], index: m.index }`)).toEqual( { full: "abb", group: "bb", index: 2, }, ) expect(await value(`return /a/.exec("zzz")`)).toBeNull() }) test("named groups read through", async () => { expect( await value(`const m = /(?[a-z]+)-(?\\d+)/.exec("id ab-42"); return m.groups.word + m.groups.num`), ).toBe("ab42") }) test("global exec advances lastIndex across calls", async () => { expect( await value(` const r = /\\d+/g const first = r.exec("a1b22c") const second = r.exec("a1b22c") return [first[0], second[0]] `), ).toEqual(["1", "22"]) }) test("lastIndex is writable and stores a number", async () => { expect( await value(` const pattern = /(?:ab|cd)\\d?/g pattern.lastIndex = "12" const stored = [pattern.lastIndex, typeof pattern.lastIndex] const match = pattern.exec("aacd2233ab12nm444ab42") pattern.lastIndex = 0 return [stored, match[0], match.index, pattern.lastIndex] `), ).toEqual([[12, "number"], "ab4", 17, 0]) // lastIndex is a prototype accessor, so delete is a no-op rather than a TypeError. expect(await value(`const re = /a/; return [delete re.lastIndex, re.lastIndex]`)).toEqual([true, 0]) }) test("a non-numeric lastIndex runs from 0; non-global exec and test leave it alone", async () => { expect( await value(` const pattern = /a/g pattern.lastIndex = {} const match = pattern.exec("ba") pattern.lastIndex = 10 const missed = pattern.exec("a") const plain = /a/ plain.lastIndex = 5 return [match.index, pattern.lastIndex, missed, plain.exec("ba").index, plain.test("ba"), plain.lastIndex] `), ).toEqual([1, 0, null, 1, true, 5]) }) test("String methods read and update lastIndex like exec", async () => { expect( await value(` const re = /a/y re.exec("aa") re.lastIndex = 0 return ["aa".replace(re, "b"), re.lastIndex] `), ).toEqual(["ba", 1]) expect( await value(` const re = /a/g re.exec("aaa") return ["aaa".match(re), re.lastIndex] `), ).toEqual([["a", "a", "a"], 0]) expect( await value(` const re = /a/g re.lastIndex = 2 return ["aaa".replace(re, () => "b"), re.lastIndex] `), ).toEqual(["bbb", 0]) expect( await value(` const re = /a/g re.lastIndex = 2 return ["aaa".replaceAll(re, "b"), re.lastIndex] `), ).toEqual(["bbb", 0]) expect( await value(` const re = /a/y re.lastIndex = 1 const m = "baa".match(re) return [m.index, re.lastIndex] `), ).toEqual([1, 2]) }) test("split, search, and matchAll leave lastIndex unchanged like JS", async () => { expect( await value(` const re = /a/y re.lastIndex = 2 return ["banana".split(re), re.lastIndex] `), ).toEqual([["b", "n", "n", ""], 2]) expect( await value(` const re = /a/g re.lastIndex = 2 return ["banana".search(re), re.lastIndex] `), ).toEqual([1, 2]) expect( await value(` const re = /a/y re.lastIndex = 2 return ["banana".search(re), re.lastIndex] `), ).toEqual([-1, 2]) expect( await value(` const re = /a/g re.lastIndex = 2 return ["banana".matchAll(re).map((m) => m.index), re.lastIndex] `), ).toEqual([[3, 5], 2]) expect( await value(` const re = /a/gy re.lastIndex = 1 return ["banana".matchAll(re).map((m) => m.index), re.lastIndex] `), ).toEqual([[1], 1]) }) test("String methods leave lastIndex untouched without g or y", async () => { expect( await value(` const re = /a/ re.lastIndex = 5 return ["aaa".replace(re, "b"), "aaa".match(re).index, "aaa".split(re), "aaa".search(re), re.lastIndex] `), ).toEqual(["baa", 0, ["", "", "", ""], 0, 5]) }) test("an unmatched string pattern returns null", async () => { expect(await value(`return "abc".match(/\\d/)`)).toBeNull() }) test("matchAll materializes match arrays with captures", async () => { expect(await value(`return "a1b22".matchAll(/(\\d+)/g).map((m) => m[1])`)).toEqual(["1", "22"]) }) test("function replacers receive captures, offsets, input, and named groups", async () => { expect( await value(` const seen = [] const output = "a1b22".replace(/(\\d)(\\d)?/g, (match, first, second, offset, input) => { seen.push([match, first, second === undefined, offset, input]) return Number(match) * 2 }) return { output, seen } `), ).toEqual({ output: "a2b44", seen: [ ["1", "1", true, 1, "a1b22"], ["22", "2", false, 3, "a1b22"], ], }) expect( await value(` return "red-blue".replace( /(?[a-z]+)-(?[a-z]+)/, (match, left, right, offset, input, groups) => groups.right + ":" + groups.left, ) `), ).toBe("blue:red") }) test("function replacers support string searches, zero-length matches, and result coercion", async () => { expect(await value(`return "banana".replace("na", (match, offset, input) => "[" + offset + "]")`)).toBe("ba[2]na") expect(await value(`return "ab".replaceAll("", (match, offset) => offset)`)).toBe("0a1b2") expect(await value(`return "😀".replaceAll(/(?:)/gu, (match, offset) => "[" + offset + "]")`)).toBe("[0]😀[2]") expect( await value(`return "123".replace(/\\d/g, (match) => match === "1" ? 7 : match === "2" ? null : { n: 3 })`), ).toBe("7null[object Object]") }) test("promise-returning string replacers are coerced synchronously", async () => { const decorate = Tool.make({ description: "Decorate a string", input: Schema.String, output: Schema.String, execute: (input) => Effect.succeed(`[${input}]`), }) const result = await Effect.runPromise( CodeMode.execute({ tools: { host: { decorate } }, code: `return "a1b22".replace(/\\d+/g, async (match) => await tools.host.decorate(match))`, }), ) expect(result.ok && result.value).toBe("a[object Promise]b[object Promise]") const missingAwait = await Effect.runPromise( CodeMode.execute({ tools: { host: { decorate } }, code: `return "a1".replace(/\\d/, (match) => tools.host.decorate(match))`, }), ) expect(missingAwait.ok && missingAwait.value).toBe("a[object Promise]") }) test("replaceAll without the g flag is a catchable error", async () => { expect(await value(`try { "a".replaceAll(/a/, "b"); return "no" } catch { return "caught" }`)).toBe("caught") }) test("new RegExp constructs from strings; invalid patterns are catchable", async () => { expect(await value(`return new RegExp("a+", "i").test("AAA")`)).toBe(true) expect(await value(`try { new RegExp("("); return "no" } catch { return "caught" }`)).toBe("caught") expect(await value(`return [/a/ instanceof RegExp, /a/.source]`)).toEqual([true, "a"]) }) test("invalid patterns fail with actionable messages", async () => { const fromString = await error(`return "abc".match("(")`) expect(fromString.message).toContain('String.match received the string "("') expect(fromString.message).toContain("escape them with a backslash") const fromConstructor = await error(`return new RegExp("(")`) expect(fromConstructor.message).toContain('new RegExp(...) received "("') expect(fromConstructor.message).toContain("escape them with a backslash") const fromFlags = await error(`return new RegExp("a", "xz")`) expect(fromFlags.message).toContain('invalid flags "xz"') expect(fromFlags.message).toContain("Valid flags are") }) test("missing g-flag errors say how to fix the call", async () => { expect((await error(`return "aa".replaceAll(/a/, "b")`)).message).toContain("write /a/g, or use String.replace") expect((await error(`return "aa".matchAll(/a/)`)).message).toContain("write /a/g, or use String.match") }) test("any argument is a pattern string, as new RegExp(arg) reads it", async () => { expect( await value( `return ["a42b".match(42)[0], "xnullx".search(null), "abc".match(undefined), [..."1a1".matchAll(1)].length]`, ), ).toEqual(["42", 1, [""], 2]) }) test("source and flags properties read through", async () => { expect(await value(`const r = /ab/gi; return { source: r.source, flags: r.flags, global: r.global }`)).toEqual({ source: "ab", flags: "gi", global: true, }) }) test("regexes serialize as {} like JSON.stringify, at the boundary and inside the program", async () => { expect(await value(`return [/a/, { r: /b/gi }]`)).toEqual([{}, { r: {} }]) expect(await value(`return JSON.stringify({ r: /a/g })`)).toBe('{"r":{}}') }) test("template interpolation renders the literal form", async () => { expect(await value("return `${/ab/g}`")).toBe("/ab/g") }) }) describe("URL and URI helpers", () => { test("encodes and decodes complete URIs and URI components", async () => { expect( await value(` return [ encodeURI("https://example.test/a b?q=a/b"), encodeURIComponent("a b/c?"), decodeURI("https://example.test/a%20b?q=a/b"), decodeURIComponent("a%20b%2Fc%3F"), ["a b", "c/d"].map(encodeURIComponent), ] `), ).toEqual([ "https://example.test/a%20b?q=a/b", "a%20b%2Fc%3F", "https://example.test/a b?q=a/b", "a b/c?", ["a%20b", "c%2Fd"], ]) expect( await value(`try { decodeURIComponent("%zz"); return false } catch (error) { return error instanceof URIError }`), ).toBe(true) }) test("resolves and mutates URLs with linked search parameters", async () => { expect( await value(` const url = new URL("../users?id=old#top", "https://user:pass@example.com:8443/api/v1/") url.pathname = "/items/a b" url.searchParams.set("id", "a b") url.searchParams.append("tag", "x/y") url.hash = "part 1" return { href: url.href, origin: url.origin, host: url.host, pathname: url.pathname, search: url.search, id: url.searchParams.get("id"), string: String(url), json: url.toJSON(), instances: [ url instanceof URL, url.searchParams instanceof URLSearchParams, url.searchParams === url.searchParams, ], } `), ).toEqual({ href: "https://user:pass@example.com:8443/items/a%20b?id=a+b&tag=x%2Fy#part%201", origin: "https://example.com:8443", host: "example.com:8443", pathname: "/items/a%20b", search: "?id=a+b&tag=x%2Fy", id: "a b", string: "https://user:pass@example.com:8443/items/a%20b?id=a+b&tag=x%2Fy#part%201", json: "https://user:pass@example.com:8443/items/a%20b?id=a+b&tag=x%2Fy#part%201", instances: [true, true, true], }) }) test("URLSearchParams supports records, pairs, mutation, callbacks, and materialization", async () => { expect( await value(` const params = new URLSearchParams([["tag", "b"], ["tag", "a"], ["q", "a b"]]) const seen = [] params.forEach((value, key) => seen.push(key + "=" + value)) params.delete("tag", "b") params.append("tag", "c") params.sort() return { text: params.toString(), size: params.size, tags: params.getAll("tag"), has: params.has("tag", "c"), entries: Array.from(params), object: Object.fromEntries(params), record: new URLSearchParams({ page: 2, filter: "open" }).toString(), seen, } `), ).toEqual({ text: "q=a+b&tag=a&tag=c", size: 3, tags: ["a", "c"], has: true, entries: [ ["q", "a b"], ["tag", "a"], ["tag", "c"], ], object: { q: "a b", tag: "c" }, record: "page=2&filter=open", seen: ["tag=b", "tag=a", "q=a b"], }) }) test("URL parsing failures are catchable and values use native JSON forms", async () => { expect( await value(` const parsed = URL.parse("/users", "https://example.test/api/") let invalidIsTypeError = false try { new URL("not relative without a base") } catch (error) { invalidIsTypeError = error instanceof TypeError } return { canParse: URL.canParse("/users", "https://example.test/api/"), cannotParse: URL.canParse("not relative without a base"), parsed: parsed.href, invalidIsTypeError, boundary: [new URL("https://example.test/a"), new URLSearchParams("q=one")], json: JSON.stringify({ url: new URL("https://example.test/a"), params: new URLSearchParams("q=one") }), } `), ).toEqual({ canParse: true, cannotParse: false, parsed: "https://example.test/users", invalidIsTypeError: true, boundary: ["https://example.test/a", "q=one"], json: '{"url":"https://example.test/a","params":{}}', }) }) test("distinguishes omitted URL arguments from explicit undefined", async () => { expect( await value(` function throwsTypeError(run) { try { run(); return false } catch (error) { return error instanceof TypeError } } const params = new URLSearchParams() const required = [ () => params.append(), () => params.delete(), () => params.get(), () => params.getAll(), () => params.has(), () => params.set(), () => params.forEach(), ].map(throwsTypeError) params.append(undefined, undefined) return { construct: throwsTypeError(() => new URL()), canParse: throwsTypeError(() => URL.canParse()), parse: throwsTypeError(() => URL.parse()), explicitUndefined: new URL(undefined, "https://example.test/base/").href, params: params.toString(), required, } `), ).toEqual({ construct: true, canParse: true, parse: true, explicitUndefined: "https://example.test/base/undefined", params: "undefined=undefined", required: [true, true, true, true, true, true, true], }) }) }) describe("Headers", () => { test("constructs from records, pairs, Maps, and Headers; names fold to lowercase and values combine", async () => { expect( await value(` const headers = new Headers({ "Content-Type": "text/plain", "X-Count": 1, "X-Null": null }) headers.append("Accept", "text/html") headers.append("accept", "application/json") headers.set("x-count", "2") headers.delete("x-null") const copy = new Headers(headers) copy.set("content-type", "text/html") return { get: headers.get("content-type"), missing: headers.get("x-missing"), combined: headers.get("ACCEPT"), has: [headers.has("Accept"), headers.has("x-null")], count: headers.get("x-count"), copied: [headers.get("content-type"), copy.get("content-type")], pairs: [...new Headers([["b", "2"], ["A", "1"]])], map: [...new Headers(new Map([["k", "v"]]))], keys: [...headers.keys()], values: [...headers.values()], entries: [...headers.entries()], } `), ).toEqual({ get: "text/plain", missing: null, combined: "text/html, application/json", has: [true, false], count: "2", copied: ["text/plain", "text/html"], pairs: [ ["a", "1"], ["b", "2"], ], map: [["k", "v"]], keys: ["accept", "content-type", "x-count"], values: ["text/html, application/json", "text/plain", "2"], entries: [ ["accept", "text/html, application/json"], ["content-type", "text/plain"], ["x-count", "2"], ], }) }) test("iterates in sorted order everywhere iteration is allowed, and getSetCookie keeps cookies apart", async () => { expect( await value(` const headers = new Headers({ b: "2", a: "1" }) headers.append("Set-Cookie", "x=1") headers.append("set-cookie", "y=2") const seen = [] headers.forEach((value, name, self) => seen.push(name + "=" + value + ":" + (self === headers))) const [first] = headers function* pairs() { yield* headers } return { seen, first, spread: [...headers], from: Array.from(headers).length, generator: [...pairs()].length, object: Object.fromEntries(headers), cookies: headers.getSetCookie(), } `), ).toEqual({ seen: ["a=1:true", "b=2:true", "set-cookie=x=1:true", "set-cookie=y=2:true"], first: ["a", "1"], spread: [ ["a", "1"], ["b", "2"], ["set-cookie", "x=1"], ["set-cookie", "y=2"], ], from: 4, generator: 4, object: { a: "1", b: "2", "set-cookie": "y=2" }, cookies: ["x=1", "y=2"], }) }) test("serializes as a name-to-value object at the boundary and in JSON; prints for console", async () => { const result = await run(` const headers = new Headers({ "X-A": "1", b: "2" }) console.log(headers) return { headers, json: JSON.stringify({ headers }), text: String(headers), type: typeof headers, is: headers instanceof Headers } `) expect(result.ok && result.value).toEqual({ headers: { b: "2", "x-a": "1" }, json: '{"headers":{"b":"2","x-a":"1"}}', text: "[object Headers]", type: "object", is: true, }) expect(result.ok && result.logs?.[0]).toBe('Headers {"b":"2","x-a":"1"}') }) test("rejects what it cannot build from, and invalid names and values, with TypeErrors the program can catch", async () => { expect( await value(` function message(run) { try { run(); return null } catch (error) { return error instanceof TypeError ? error.message : error } } const headers = new Headers() return [ message(() => Headers()), message(() => new Headers(null)), message(() => new Headers(1)), message(() => new Headers("a=1")), message(() => new Headers(new Date())), message(() => new Headers(() => 1)), message(() => new Headers([["name"]])), message(() => new Headers([["a", "b", "c"]])), message(() => new Headers({ "bad name": "x" })), message(() => new Headers({ name: "bad\u0000value" })), message(() => headers.get("invalid\u0100")), message(() => headers.has({})), message(() => headers.set("a", "invalid\u0100")), message(() => headers.append("a")), message(() => headers.forEach()), message(() => headers.forEach(1)), message(() => { const get = headers.get; return get("a") }), ] `), ).toEqual([ "Constructor Headers requires 'new'.", "new Headers(...) expects a record of names to values, iterable [name, value] pairs, or Headers.", "new Headers(...) expects a record of names to values, iterable [name, value] pairs, or Headers.", "new Headers(...) expects a record of names to values, iterable [name, value] pairs, or Headers.", "new Headers(...) expects a record of names to values, iterable [name, value] pairs, or Headers.", "new Headers(...) expects a record of names to values, iterable [name, value] pairs, or Headers.", "new Headers(...) expects iterable [name, value] pairs.", "new Headers(...) expects iterable [name, value] pairs.", expect.stringContaining("bad name"), expect.stringContaining("invalid value"), expect.stringContaining("Invalid header name"), expect.stringContaining("[object Object]"), expect.stringContaining("invalid value"), "Headers.append requires 2 arguments.", "Headers.forEach requires 1 argument.", "Headers.forEach expects a function callback.", "Headers.prototype.get called on incompatible receiver undefined.", ]) }) }) describe("Map", () => { test("get/set/has/size with chaining", async () => { expect( await value(` const m = new Map() m.set("a", 1).set("b", 2) return { a: m.get("a"), b: m.get("b"), has: m.has("a"), miss: m.get("zz") === undefined, size: m.size } `), ).toEqual({ a: 1, b: 2, has: true, miss: true, size: 5 - 3 }) }) test("object keys use identity", async () => { expect( await value(` const key = { id: 1 } const m = new Map() m.set(key, "hit") return [m.get(key), m.get({ id: 1 }) === undefined] `), ).toEqual(["hit", true]) }) test("construction from entry pairs and another Map", async () => { expect(await value(`const m = new Map([["a", 1], ["b", 2]]); return m.get("b")`)).toBe(2) expect( await value( `const m = new Map([["a", 1]]); const n = new Map(m); n.set("b", 2); return [n.get("a"), n.get("b"), m.has("b")]`, ), ).toEqual([1, 2, false]) expect((await error(`return new Map("nope")`)).message).toMatch(/\[key, value\] pairs/) expect((await error(`return new Map(["flat"])`)).message).toMatch(/\[key, value\] pairs/) }) test("keys/values/entries return live iterators", async () => { expect( await value(` const m = new Map([["a", 1], ["b", 2]]) const keys = m.keys() const first = keys.next() m.set("c", 3) return { first, rest: [...keys], values: [...m.values()], entries: [...m.entries()], same: [...m[Symbol.iterator]()] } `), ).toEqual({ first: { value: "a", done: false }, rest: ["b", "c"], values: [1, 2, 3], entries: [ ["a", 1], ["b", 2], ["c", 3], ], same: [ ["a", 1], ["b", 2], ["c", 3], ], }) }) test("Object.fromEntries(map) and Array.from(map)", async () => { expect(await value(`return Object.fromEntries(new Map([["a", 1], ["b", 2]]))`)).toEqual({ a: 1, b: 2 }) expect(await value(`return Array.from(new Map([["a", 1]]))`)).toEqual([["a", 1]]) }) test("for...of iterates [key, value] pairs with destructuring", async () => { expect( await value(` const m = new Map([["a", 1], ["b", 2]]) let total = 0 let names = "" for (const [key, count] of m) { names += key; total += count } return names + total `), ).toBe("ab3") }) test("spread produces entry pairs", async () => { expect(await value(`return [...new Map([["a", 1]])]`)).toEqual([["a", 1]]) }) test("forEach passes (value, key)", async () => { expect( await value(` const m = new Map([["a", 1], ["b", 2]]) const seen = [] m.forEach((count, key) => seen.push(key + count)) return seen `), ).toEqual(["a1", "b2"]) }) test("delete and clear", async () => { expect( await value(` const m = new Map([["a", 1], ["b", 2]]) const removed = m.delete("a") const missed = m.delete("zz") const sizeAfterDelete = m.size m.clear() return [removed, missed, sizeAfterDelete, m.size] `), ).toEqual([true, false, 1, 0]) }) test("counting idiom: grouped tallies", async () => { expect( await value(` const words = ["a", "b", "a", "c", "a"] const counts = new Map() for (const word of words) counts.set(word, (counts.get(word) ?? 0) + 1) return Object.fromEntries(counts) `), ).toEqual({ a: 3, b: 1, c: 1 }) }) test("getOrInsert and getOrInsertComputed insert only when the key is missing", async () => { expect( await value(` const groups = new Map() groups.getOrInsert("a", []).push(1) groups.getOrInsert("a", []).push(2) let calls = 0 const computed = (key) => { calls++; return key + "!" } const first = groups.getOrInsertComputed("b", computed) const second = groups.getOrInsertComputed("b", computed) const zero = groups.getOrInsertComputed(-0, (key) => 1 / key === Infinity) return [[...groups], first, second, calls, zero] `), ).toEqual([ [ ["a", [1, 2]], ["b", "b!"], [0, true], ], "b!", "b!", 1, true, ]) expect( await value(` const m = new Map() const outer = m.getOrInsertComputed("k", () => { m.set("k", "inner"); return "outer" }) let thrown try { m.getOrInsertComputed("j", () => { throw new Error("boom") }) } catch (error) { thrown = error.message } return [outer, m.get("k"), thrown, m.has("j")] `), ).toEqual(["outer", "outer", "boom", false]) expect((await error(`new Map().getOrInsertComputed("k", 5)`)).message).toContain("expects a function callback") }) test("maps serialize to {} at the boundary, like JSON", async () => { expect(await value(`return new Map([["a", 1]])`)).toEqual({}) expect(await value(`return JSON.stringify(new Map([["a", 1]]))`)).toBe("{}") }) test("console.log renders map contents for debugging", async () => { const result = await run(`console.log(new Map([["a", 1]])); return null`) expect(result.ok).toBe(true) expect(result.logs?.[0]).toBe(`Map(1) [["a",1]]`) }) }) describe("Set", () => { test("forEach is live on Map and Set: deleted entries are skipped and added ones visited", async () => { expect( await value(` const m = new Map([[1, "a"], [2, "b"]]) const s = new Set([1, 2]) const seen = [] m.forEach((v, k) => { seen.push(k); if (k === 1) { m.delete(2); m.set(3, "c") } }) s.forEach((v) => { seen.push(v); if (v === 1) { s.delete(2); s.add(3) } }) return seen `), ).toEqual([1, 3, 1, 3]) }) test("add/has/delete/size with chaining", async () => { expect( await value(` const s = new Set() s.add(1).add(2).add(1) const removed = s.delete(2) return [s.size, s.has(1), s.has(2), removed] `), ).toEqual([1, true, false, true]) }) test("dedupe idiom: [...new Set(items)]", async () => { expect(await value(`return [...new Set([1, 2, 2, 3, 1])]`)).toEqual([1, 2, 3]) }) test("construction from strings and other Sets", async () => { expect(await value(`return [...new Set("aba")]`)).toEqual(["a", "b"]) expect(await value(`return Array.from(new Set(new Set([1, 2])))`)).toEqual([1, 2]) }) test("SameValueZero: NaN is findable", async () => { expect(await value(`const s = new Set([NaN]); return s.has(NaN)`)).toBe(true) }) test("for...of iterates values", async () => { expect( await value(` let total = 0 for (const n of new Set([1, 2, 3])) total += n return total `), ).toBe(6) }) test("sets cross the boundary as arrays; JSON.stringify keeps {} like JS", async () => { expect(await value(`return { s: new Set([1, "a", { n: 1 }, undefined]) }`)).toEqual({ s: [1, "a", { n: 1 }, null] }) expect(await value(`return JSON.stringify(new Set([1]))`)).toBe("{}") }) }) describe("Uint8Array", () => { test("constructs from a length, an array, an iterable, or another Uint8Array", async () => { expect( await value(` const a = new Uint8Array(2) const b = new Uint8Array([1, 2, 300]) const c = Uint8Array.from(new Set([7, 8])) const d = new Uint8Array(b) d[0] = 9 return [[...a], [...b], [...c], [...d], [...b], new Uint8Array("3").length, [...Uint8Array.of(1, "2")]] `), ).toEqual([[0, 0], [1, 2, 44], [7, 8], [9, 2, 44], [1, 2, 44], 3, [1, 2]]) expect((await error(`new Uint8Array(-1)`)).message).toContain("Invalid typed array length: -1") expect((await error(`new Uint8Array(1.5)`)).message).toContain("Invalid typed array length") expect((await error(`new Uint8Array(20_000_000)`)).message).toContain("Invalid array length") expect((await error(`Uint8Array.from(/x/)`)).message).toContain("received a RegExp") }) test("index reads and writes clamp to a byte and ignore out-of-range writes, like JS", async () => { expect( await value(` const b = new Uint8Array(3) b[0] = 300 b[1] = -1 b["2"] = "7" b[5] = 9 b.tag = "x" return [b[0], b[1], b[2], b[5], b.length, Object.keys(b), 1 in b, 5 in b, b.tag] `), ).toEqual([44, 255, 7, null, 3, ["0", "1", "2", "tag"], true, false, "x"]) expect((await error(`delete new Uint8Array(1)[0]`)).message).toContain("Cannot delete property '0'") expect((await error(`Uint8Array.prototype.length`)).message).toContain("incompatible receiver") }) test("iteration, spread, destructuring, and Array.from", async () => { expect( await value(` const b = new Uint8Array([1, 2, 3]) const [x, ...rest] = b const seen = [] for (const byte of b) seen.push(byte) function* g() { yield* b } return [x, rest, seen, [...g()], Array.from(b, (n) => n * 2), new Set(b).size, Array.isArray(b)] `), ).toEqual([1, [2, 3], [1, 2, 3], [1, 2, 3], [2, 4, 6], 3, false]) }) test("methods", async () => { expect( await value(` const b = Uint8Array.from([1, 2, 3, 2]) const view = b.subarray(1, 3) view.fill(9) const copy = b.slice(0, 2) copy[0] = 5 const target = new Uint8Array(4) target.set([1, 2], 2) target.set(b.subarray(0, 1)) return [ b.at(-1), [...b], [...copy], [...target], b.indexOf(9), b.lastIndexOf(9), b.includes(3), b.includes(2, 1), b.join("-"), b.toString(), [...b.keys()], [...b.values()], [...b.entries()], [...Uint8Array.from([1, 2]).reverse()], ] `), ).toEqual([ 2, [1, 9, 9, 2], [5, 9], [1, 0, 1, 2], 1, 2, false, true, "1-9-9-2", "1,9,9,2", [0, 1, 2, 3], [1, 9, 9, 2], [ [0, 1], [1, 9], [2, 9], [3, 2], ], [2, 1], ]) expect((await error(`new Uint8Array(2).set([1, 2, 3])`)).message).toContain("does not fit") expect((await error(`new Uint8Array(2).set([1], 2)`)).message).toContain("does not fit") }) test("base64 and hex", async () => { expect( await value(` return [ new Uint8Array([104, 105]).toBase64(), new Uint8Array([255, 0]).toHex(), [...Uint8Array.fromBase64("aGk=")], [...Uint8Array.fromHex("ff00")], ] `), ).toEqual(["aGk=", "ff00", [104, 105], [255, 0]]) expect(await error(`Uint8Array.fromBase64("!!!")`)).toMatchObject({ message: expect.stringContaining("SyntaxError"), }) expect(await error(`Uint8Array.fromHex("zz")`)).toMatchObject({ message: expect.stringContaining("SyntaxError") }) }) test("coercion, console, JSON, and Object.prototype.toString", async () => { expect( await value(` const b = new Uint8Array([1, 2]) console.log(b, new Uint8Array()) return [String(b), b + "", +new Uint8Array([5]), Number.isNaN(Number(b)), b == "1,2", JSON.stringify(b), b.toLocaleString(), typeof b, b instanceof Uint8Array] `), ).toEqual(["1,2", "1,2", 5, true, true, '{"0":1,"1":2}', "1,2", "object", true]) expect((await run(`console.log(new Uint8Array([1, 2]), new Uint8Array())`)).logs).toEqual([ "Uint8Array(2) [1,2] Uint8Array(0) []", ]) }) test("cannot cross the tool boundary; the error says how to encode it", async () => { expect((await error(`return new Uint8Array(1)`)).message).toContain("pass text instead") expect((await error(`return { deep: [new Uint8Array(1)] }`)).message).toContain("bytes.toBase64()") expect(await value(`return new Uint8Array([7, 8]).toBase64()`)).toBe("Bwg=") }) }) describe("TextEncoder and TextDecoder", () => { test("round-trips UTF-8 and exposes the standard fields", async () => { expect( await value(` const encoder = new TextEncoder() const bytes = encoder.encode("héllo ✓") const decoder = new TextDecoder() return [ [...bytes], decoder.decode(bytes), decoder.decode(), [...encoder.encode()], encoder.encoding, decoder.encoding, decoder.fatal, decoder.ignoreBOM, new TextDecoder("UTF8", { fatal: true }).fatal, new TextDecoder().decode(new Uint8Array([0xef, 0xbb, 0xbf, 0x41])), new TextDecoder("utf-8", { ignoreBOM: true }).decode(new Uint8Array([0xef, 0xbb, 0xbf, 0x41])), new TextDecoder().decode(new Uint8Array([0xff])), encoder instanceof TextEncoder, decoder instanceof TextDecoder, ] `), ).toEqual([ [104, 195, 169, 108, 108, 111, 32, 226, 156, 147], "héllo ✓", "", [], "utf-8", "utf-8", false, false, true, "A", "\ufeffA", "\ufffd", true, true, ]) }) test("only UTF-8 is supported; fatal decoding rejects malformed input", async () => { expect((await error(`new TextDecoder("latin1")`)).message).toContain('The "latin1" encoding is not supported') expect((await error(`new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array([0xff]))`)).message).toContain( "not valid utf-8", ) expect((await error(`new TextDecoder().decode("text")`)).message).toContain( "expects a Uint8Array, received a string", ) expect((await error(`TextDecoder()`)).message).toContain("requires 'new'") }) test("crypto.getRandomValues fills the given bytes in place", async () => { expect( await value( `const b = new Uint8Array(16); const same = crypto.getRandomValues(b) === b; return [same, b.length, b.some((n) => n !== 0)]`, ), ).toEqual([true, 16, true]) expect((await error(`crypto.getRandomValues([1])`)).message).toContain("expects a Uint8Array, received an array") }) }) describe("Iterator helpers", () => { test("lazy helpers chain over collection iterators and generators, one source step per result", async () => { expect( await value(` const pulled = [] function* naturals() { let n = 0; while (true) { pulled.push(n); yield n++ } } const squares = naturals().map((n) => n * n).filter((n) => n % 2 === 0).drop(1).take(3) const first = squares.next() return { first, rest: squares.toArray(), after: squares.next(), pulled, values: new Map([["a", 1], ["b", 2]]).values().map((v, i) => v * 10 + i).toArray(), flat: [1, 2].values().flatMap((n) => [n, [n]]).toArray(), entries: [...new Map([[1, 2]]).entries().map(([k, v]) => k + v)], } `), ).toEqual({ first: { value: 4, done: false }, rest: [16, 36], after: { done: true }, pulled: [0, 1, 2, 3, 4, 5, 6], values: [10, 21], flat: [1, [1], 2, [2]], entries: [3], }) }) test("eager helpers consume the source and close it on early exit", async () => { expect( await value(` const log = [] function* g() { try { yield 1; yield 2; yield 3 } finally { log.push("closed") } } const seen = [] g().forEach((v, i) => seen.push([v, i])) return { seen, sum: g().reduce((a, b) => a + b), sumFrom: g().reduce((a, b) => a + b, 10), some: g().some((v) => v === 2), every: g().every((v) => v < 2), find: g().find((v) => v > 1), missing: g().find((v) => v > 5), log, } `), ).toEqual({ seen: [ [1, 0], [2, 1], [3, 2], ], sum: 6, sumFrom: 16, some: true, every: false, find: 2, log: ["closed", "closed", "closed", "closed", "closed", "closed", "closed"], }) }) test("a helper closes its source when a callback throws, on return(), and on early for...of exit", async () => { expect( await value(` const log = [] function* g(name) { try { yield 1; yield 2 } finally { log.push(name) } } const throwing = g("throw").map((v) => { if (v === 2) throw new Error("boom"); return v }) throwing.next() let message try { throwing.next() } catch (error) { message = error.message } const returned = g("return").map((v) => v) returned.next() const closed = returned.return() for (const v of g("loop").filter((v) => true)) break return { message, afterThrow: throwing.next(), closed, afterReturn: returned.next(), log } `), ).toEqual({ message: "boom", afterThrow: { done: true }, closed: { done: true }, afterReturn: { done: true }, log: ["throw", "return", "loop"], }) }) test("collection iterators have no return() and continue after an early exit, as in JS", async () => { expect( await value(` const it = [1, 2, 3].values() const found = it.some((v) => v === 2) return [typeof it.return, found, it.next().value, typeof it.map(x => x).return] `), ).toEqual(["undefined", true, 3, "function"]) }) test("Iterator.from and the abstract Iterator constructor", async () => { expect( await value(` const it = [1].values() let n = 0 const errors = [] for (const attempt of [() => Iterator(), () => new Iterator(), () => Iterator.from(5)]) { try { attempt() } catch (error) { errors.push(error.name) } } return [ Iterator.from(it) === it, Iterator.from("ab").toArray(), Iterator.from([1, 2]).map((v) => v * 2).toArray(), Iterator.from({ next: () => ({ done: n > 1, value: n++ }) }).toArray(), it instanceof Iterator, errors, ] `), ).toEqual([true, ["a", "b"], [2, 4], [0, 1], true, ["TypeError", "TypeError", "TypeError"]]) }) test("argument validation", async () => { const cases: Array<[string, string]> = [ ["[1].values().map(1)", "Iterator.prototype.map expects a function callback."], ["[1].values().take(-1)", "Iterator.prototype.take expects a non-negative count, received -1."], ["[1].values().drop()", "Iterator.prototype.drop expects a non-negative count, received NaN."], [ "[1].values().flatMap((v) => 'ab').toArray()", "Iterator.prototype.flatMap expects an iterable or iterator, received a string.", ], ["[].values().reduce((a, b) => a)", "Iterator.prototype.reduce of an empty iterator with no initial value."], ] for (const [code, message] of cases) { expect((await error(`return ${code}`)).message).toContain(message) } expect( await error(` function* g() { while (true) yield 1 } const it = g().map(() => it.next()) return it.next() `), ).toMatchObject({ message: expect.stringContaining("Iterator helper is already running.") }) }) }) describe("built-in iterators", () => { test("keys/values/entries and [Symbol.iterator] step with next() and stay live", async () => { expect( await value(` const items = ["a"] const it = items.entries() items.push("b") const steps = [it.next(), it.next(), it.next()] items.push("c") return { steps, after: it.next(), same: items[Symbol.iterator] === items.values } `), ).toEqual({ steps: [{ value: [0, "a"], done: false }, { value: [1, "b"], done: false }, { done: true }], after: { done: true }, same: true, }) expect( await value(` const s = new Set([1, 2]) const u = new URLSearchParams("a=1&b=2") const h = new Headers({ b: "2", a: "1" }) const bytes = new Uint8Array([7, 8]) return [ [...s.entries()], [...s[Symbol.iterator]()], s[Symbol.iterator] === s.values, [...u.keys()], [...u[Symbol.iterator]()], u[Symbol.iterator] === u.entries, [...h.values()], [...h[Symbol.iterator]()], h[Symbol.iterator] === h.entries, [...bytes.entries()], [...bytes[Symbol.iterator]()], bytes[Symbol.iterator] === bytes.values, [..."ab"[Symbol.iterator]()], ] `), ).toEqual([ [ [1, 1], [2, 2], ], [1, 2], true, ["a", "b"], [ ["a", "1"], ["b", "2"], ], true, ["1", "2"], [ ["a", "1"], ["b", "2"], ], true, [ [0, 7], [1, 8], ], [7, 8], true, ["a", "b"], ]) }) test("iterators are consumed once by every iteration site", async () => { expect( await value(` const it = [1, 2, 3, 4].values() const picked = [] for (const item of it) { picked.push(item); if (item === 2) break } const [third] = it return { picked, third, rest: [...it], spent: Array.from(it), again: it[Symbol.iterator]() === it } `), ).toEqual({ picked: [1, 2], third: 3, rest: [4], spent: [], again: true }) expect( await value(` const m = new Map([["a", 1], ["b", 2]]) return [ Object.fromEntries(m.entries()), Array.from(m.keys(), (k) => k + "!"), new Set(m.values()).size, await Promise.all([Promise.resolve(1), 2].values()), ] `), ).toEqual([{ a: 1, b: 2 }, ["a!", "b!"], 2, [1, 2]]) expect(await value(`let s = 0; for await (const v of [Promise.resolve(1), 2].values()) s += v; return s`)).toBe(3) expect( await value(`return new Set([1, 2]).union({ size: 1, has: () => false, keys: () => new Set([3]).keys() })`), ).toEqual([1, 2, 3]) }) test("iterators are opaque references", async () => { expect(await value(`return [1].keys()`)).toEqual({}) expect(await value(`return JSON.stringify({ it: [1].keys() })`)).toBe('{"it":{}}') expect(await value(`return [typeof [1].keys(), Array.isArray([1].keys()), Object.keys([1].keys())]`)).toEqual([ "object", false, [], ]) const logged = await run(`console.log([1].keys()); return null`) expect(logged.logs?.[0]).toBe("[opaque reference]") expect((await error(`return [1].keys() + ""`)).message).toContain("Binary operators require data values") expect((await error(`return [1].keys().next.call({})`)).message).toContain( "Iterator.prototype.next called on incompatible receiver a data object", ) expect((await error(`const it = [1].keys(); const next = it.next; return next()`)).message).toContain( "Iterator.prototype.next called on incompatible receiver undefined", ) }) }) describe("Object.prototype.toString", () => { test("reports the built-in kind it is inherited by, as JS does through Symbol.toStringTag", async () => { expect( await value(` return [ new Map().toString(), new Set().toString(), new Headers().toString(), Promise.resolve(1).toString(), [1].values().toString(), ({}).toString(), String(new Map()), String(Promise.resolve(1)), \`\${new Set([1])}\`, [new Map()] + "", new Map() == "[object Map]", ] `), ).toEqual([ "[object Map]", "[object Set]", "[object Headers]", "[object Promise]", "[object Iterator]", "[object Object]", "[object Map]", "[object Promise]", "[object Set]", "[object Map]", true, ]) }) }) describe("console.log of errors", () => { test("prints name and message, nested too", async () => { const result = await run(`console.log(new Error("boom"), { e: new RangeError("r") })`) expect(result.logs).toEqual(['Error: boom {"e":RangeError: r}']) }) }) describe("toLocaleString", () => { test("numbers and dates format as en-US in UTC; everything else falls back to toString", async () => { expect( await value(` return [ (1234567.891).toLocaleString(), new Date(0).toLocaleString(), new Date(0).toLocaleDateString(), new Date(0).toLocaleTimeString(), "a".toLocaleString(), true.toLocaleString(), ({}).toLocaleString(), ({ toString: () => "custom" }).toLocaleString(), new Uint8Array([1, 2]).toLocaleString(), ] `), ).toEqual([ "1,234,567.891", "1/1/1970, 12:00:00 AM", "1/1/1970", "12:00:00 AM", "a", "true", "[object Object]", "custom", "1,2", ]) }) test("arrays join each element's toLocaleString, skipping holes and nullish elements", async () => { expect( await value(` let calls = 0 const item = { toLocaleString() { calls++; return "o" } } return [[1234.5, "x", null, undefined, item, new Date(0)].toLocaleString(), [, item, , item].toLocaleString(), calls] `), ).toEqual(["1,234.5,x,,,o,1/1/1970, 12:00:00 AM", ",o,,o", 3]) expect((await error(`const f = ({}).toLocaleString; f()`)).message).toContain( "Object.prototype.toLocaleString called on null or undefined", ) }) test("toLocaleLowerCase and toLocaleUpperCase ignore the locale argument", async () => { expect( await value(`return ["ABC".toLocaleLowerCase("tr"), "abc".toLocaleUpperCase(), "İ".toLocaleLowerCase()]`), ).toEqual(["abc", "ABC", "i̇"]) }) }) describe("stdlib integration", () => { test("constructor follows own keys, shadowing, writes, and new", async () => { expect( await value(`return [JSON.parse('{"constructor":"Foo"}').constructor, ({ constructor: 1 }).constructor]`), ).toEqual(["Foo", 1]) expect(await value(`const Array = 5; return [].constructor.isArray([])`)).toBe(true) expect(await value(`const o = {}; o.constructor = 7; return o.constructor`)).toBe(7) expect(await value(`return new ([].constructor)(3).length`)).toBe(3) expect(await value(`return typeof ({}).constructor`)).toBe("function") expect(await value(`return ({}).constructor.constructor === Function`)).toBe(true) }) test("new dispatches on the constructor value, not its name", async () => { expect(await value(`const D = Date; return new D(0) instanceof Date`)).toBe(true) expect(await value(`const make = (C) => new C([["a", 1]]); return make(Map).get("a")`)).toBe(1) expect(await value(`const t = { M: Map }; return new t.M() instanceof Map`)).toBe(true) const shadowed = await error(`const Date = 5; return new Date()`) expect(shadowed.message).toStartWith("TypeError: Date is not a constructor.") const fn = await error(`const f = () => 1; return new f()`) expect(fn.message).toStartWith("TypeError: f cannot be constructed") }) test("Object.is uses SameValue semantics", async () => { expect( await value(` const object = {} return [ Object.is(NaN, NaN), Object.is(0, -0), Object.is(object, object), Object.is({}, {}), ] `), ).toEqual([true, false, true, false]) }) test("Object.is compares opaque runtime references by identity", async () => { expect(await value(`return [Object.is(Math.max, Math.max), Object.is(Math.max, Math.min)]`)).toEqual([true, false]) }) test("Object values and entries accept arrays", async () => { expect(await value(`return [Object.values(["a", "b"]), Object.entries(["a", "b"])]`)).toEqual([ ["a", "b"], [ ["0", "a"], ["1", "b"], ], ]) expect(await value(`const match = /a/.exec("ba"); return [Object.values(match), Object.entries(match)]`)).toEqual([ ["a", 1, "ba"], [ ["0", "a"], ["index", 1], ["input", "ba"], ], ]) expect(await value(`return Object.keys(Object.values({ match: /a/.exec("ba") })[0])`)).toEqual([ "0", "index", "input", ]) }) test("Object.fromEntries accepts every supported entry collection", async () => { expect( await value(` return [ Object.fromEntries([["a", 1]]), Object.fromEntries(new Map([["b", 2]])), Object.fromEntries(new Set([["c", 3]])), Object.fromEntries(new URLSearchParams("d=4")), Object.fromEntries([{ 0: "e", 1: 5 }]), Object.fromEntries(new Set([[{}, 6], [new Date(0), 7], [null, 8], [undefined, 9]])), ] `), ).toEqual([ { a: 1 }, { b: 2 }, { c: 3 }, { d: "4" }, { e: 5 }, { "[object Object]": 6, "1970-01-01T00:00:00.000Z": 7, null: 8, undefined: 9 }, ]) expect(await value(`try { Object.fromEntries(new Set([Math.max])); return false } catch { return true }`)).toBe( true, ) expect( await value(`try { Object.fromEntries(new Map([["fn", Math.max]])); return false } catch { return true }`), ).toBe(true) }) test("deterministic Math methods match the host runtime", async () => { const result = await value(` return [ Math.acos(0.5), Math.acosh(2), Math.asin(0.5), Math.asinh(2), Math.atan(1), Math.atan2(1, 2), Math.atanh(0.5), Math.cos(0.5), Math.cosh(0.5), Math.sin(0.5), Math.sinh(0.5), Math.tan(0.5), Math.tanh(0.5), Math.log1p(0.5), Math.expm1(0.5), Math.f16round(1.337), Math.fround(1.337), Math.clz32(1), Math.imul(2, 3), ] `) expect(result).toEqual([ Math.acos(0.5), Math.acosh(2), Math.asin(0.5), Math.asinh(2), Math.atan(1), Math.atan2(1, 2), Math.atanh(0.5), Math.cos(0.5), Math.cosh(0.5), Math.sin(0.5), Math.sinh(0.5), Math.tan(0.5), Math.tanh(0.5), Math.log1p(0.5), Math.expm1(0.5), Math.f16round(1.337), Math.fround(1.337), Math.clz32(1), Math.imul(2, 3), ]) }) test("Object.assign mutates and returns its target", async () => { expect( await value(` const target = { a: 1 } const result = Object.assign(target, { b: 2 }) return { target, result, same: target === result } `), ).toEqual({ target: { a: 1, b: 2 }, result: { a: 1, b: 2 }, same: true }) expect(await value(`try { Object.assign(null, { a: 1 }); return false } catch { return true }`)).toBe(true) }) test("Object.assign rejects direct and nested cycles", async () => { expect( await value(` const target = { kept: true } try { Object.assign(target, { self: target }) } catch { return target } return null `), ).toEqual({ kept: true }) expect( await value(` const target = { kept: true } const nested = { target } try { Object.assign(target, { nested }) } catch { return target } return null `), ).toEqual({ kept: true }) expect( await value(` const target = {} const source = {} source[Symbol.iterator] = target try { Object.assign(target, source) } catch { return Object.hasOwn(target, Symbol.iterator) } return true `), ).toBe(false) expect( await value(` const target = {} const nested = {} nested[Symbol.iterator] = target try { Object.assign(target, { nested }) } catch { return Object.hasOwn(target, "nested") } return true `), ).toBe(false) }) test("Object.assign preserves mutations before a circular field", async () => { expect( await value(` const target = {} try { Object.assign(target, { before: 1, cycle: { target }, after: 2 }) } catch { return target } return null `), ).toEqual({ before: 1 }) expect( await value(` const target = {} const marker = {} const source = {} source[Symbol.iterator] = marker source[Symbol.asyncIterator] = target try { Object.assign(target, source) } catch { return [target[Symbol.iterator] === marker, Object.hasOwn(target, Symbol.asyncIterator)] } return null `), ).toEqual([true, false]) }) test("Object.assign preserves target identity and acyclic shared aliases", async () => { expect( await value(` const shared = { count: 1 } const target = {} const result = Object.assign(target, { left: shared, right: shared }) result.left.count = 2 return [result === target, result.left === shared, result.left === result.right, shared.count] `), ).toEqual([true, true, true, 2]) }) test("assignment resolves and reads its left side before evaluating the right side", async () => { expect(await value(`let x = 1; x += (x = 5); return x`)).toBe(6) expect(await value(`let i = 0; const values = [9]; values[i++] = i; return [values, i]`)).toEqual([[1], 1]) expect(await value(`let i = 0; const values = [10, 20]; values[i++] += i; return [values, i]`)).toEqual([ [11, 20], 1, ]) }) test("typeof reports constructors as functions and never throws", async () => { expect(await value(`return typeof Map`)).toBe("function") expect(await value(`return typeof ((x) => x)`)).toBe("function") expect(await value(`return typeof Math`)).toBe("object") expect(await value(`return typeof tools`)).toBe("object") }) test("negation works on any value", async () => { expect(await value(`return !new Map()`)).toBe(false) expect(await value(`const fn = () => 1; return !fn`)).toBe(false) }) test("object spread of CodeMode values is a no-op, like JS", async () => { expect(await value(`return { ...new Map([["a", 1]]), kept: true }`)).toEqual({ kept: true }) }) test("dates inside Map values survive in-CodeMode reads", async () => { expect( await value(` const m = new Map([["start", new Date(1000)]]) return m.get("start").getTime() `), ).toBe(1000) }) test("instanceof recognizes the stdlib value types", async () => { expect( await value( `return [new Date(0) instanceof Date, /a/ instanceof RegExp, new Map() instanceof Map, new Set() instanceof Set]`, ), ).toEqual([true, true, true, true]) expect( await value(`return [[1] instanceof Array, [1] instanceof Object, ({}) instanceof Object, 5 instanceof Object]`), ).toEqual([true, true, true, false]) expect(await value(`return [new Map() instanceof Set, "s" instanceof Date]`)).toEqual([false, false]) expect( await value(`const p = Promise.resolve(1); const isPromise = p instanceof Promise; await p; return isPromise`), ).toBe(true) }) test("realistic pipeline: parse, extract with regex, dedupe, count by day", async () => { expect( await value(` const raw = '[{"at":"2024-01-01T05:00:00Z","tag":"a b"},{"at":"2024-01-01T09:00:00Z","tag":"b c"},{"at":"2024-01-02T01:00:00Z","tag":"a"}]' const rows = JSON.parse(raw) const tags = new Set() const byDay = new Map() for (const row of rows) { for (const m of row.tag.matchAll(/[a-z]+/g)) tags.add(m[0]) const day = new Date(row.at).toISOString().slice(0, 10) byDay.set(day, (byDay.get(day) ?? 0) + 1) } return { tags: [...tags].sort((a, b) => (a < b ? -1 : 1)), byDay: Object.fromEntries(byDay) } `), ).toEqual({ tags: ["a", "b", "c"], byDay: { "2024-01-01": 2, "2024-01-02": 1 } }) }) }) describe("CodeMode values at intra-CodeMode checkpoints", () => { test("Object.values/entries keep Dates usable", async () => { expect(await value(`return Object.values({ d: new Date(0) })[0].getTime()`)).toBe(0) expect(await value(`const [key, d] = Object.entries({ d: new Date(0) })[0]; return key + ":" + d.getTime()`)).toBe( "d:0", ) }) test("Object.values/entries preserve nested object identity", async () => { expect( await value(` const child = { selected: false } const rows = { a: child } Object.values(rows)[0].selected = true return child.selected `), ).toBe(true) expect( await value(` const child = { selected: false } const rows = { a: child } Object.entries(rows)[0][1].selected = true return child.selected `), ).toBe(true) }) test("Object enumeration preserves promises and callable references", async () => { expect( await value(` const pending = Promise.resolve(1) const source = { pending } return [Object.keys(source), Object.hasOwn(source, "pending"), await Object.values(source)[0], await Object.entries(source)[0][1]] `), ).toEqual([["pending"], true, 1, 1]) expect(await value(`return Object.values({ max: Math.max })[0](1, 2)`)).toBe(2) }) test("Object enumeration rejects invalid receivers and gives promises an await hint", async () => { const diagnostic = await error(`return Object.keys(Promise.resolve({ a: 1 }))`) expect(diagnostic.kind).toBe("InvalidDataValue") expect(diagnostic.message).toContain("await") expect(await value(`return Object.keys(Math)`)).toEqual([]) }) test("Object.assign keeps Maps usable", async () => { expect(await value(`const merged = Object.assign({}, { m: new Map([["a", 1]]) }); return merged.m.get("a")`)).toBe( 1, ) }) test("object and array spread keep CodeMode values usable", async () => { expect( await value(` const src = { m: new Map([["a", 1]]) } const copy = { ...src } copy.m.set("b", 2) return [copy.m.get("a"), src.m.get("b")] `), ).toEqual([1, 2]) expect(await value(`const list = [new Date(1000)]; const copy = [...list]; return copy[0].getTime()`)).toBe(1000) }) test("Array.from over arrays keeps nested CodeMode values usable", async () => { expect(await value(`return Array.from([new Date(5)])[0].getTime()`)).toBe(5) }) test("Array.from and Array.of preserve nested object identity", async () => { expect( await value(` const child = { selected: false } Array.from([child])[0].selected = true return child.selected `), ).toBe(true) expect( await value(` const child = { selected: false } Array.of(child)[0].selected = true return child.selected `), ).toBe(true) }) test("Array.from and Array.of preserve promises and callable references", async () => { expect( await value(` const pending = Promise.resolve(1) return [await Array.from([pending])[0], await Array.of(pending)[0]] `), ).toEqual([1, 1]) expect(await value(`return [Array.from([Math.max])[0](1, 2), Array.of(Math.max)[0](3, 4)]`)).toEqual([2, 4]) }) test("Array.from preserves identity across supported collection shapes", async () => { expect( await value(` const child = { selected: false } const fromArrayLike = Array.from({ 0: child, length: 1 }) const fromMap = Array.from(new Map([["child", child]])) const fromSet = Array.from(new Set([child])) fromArrayLike[0].selected = true return [fromMap[0][1] === child, fromSet[0] === child, child.selected] `), ).toEqual([true, true, true]) }) test("Array.from rejects invalid receivers and gives promises an await hint", async () => { const diagnostic = await error(`return Array.from(Promise.resolve([1]))`) expect(diagnostic.kind).toBe("InvalidDataValue") expect(diagnostic.message).toContain("await") expect(await value(`return Array.from((a, b) => 1)`)).toEqual([null, null]) }) test("regexes stay callable through Object.values", async () => { expect(await value(`return Object.values({ r: /ab+/ })[0].test("abb")`)).toBe(true) }) test("Object.* helpers see CodeMode values as empty objects, never internals", async () => { expect(await value(`return Object.keys(new Map([["a", 1]]))`)).toEqual([]) expect(await value(`return Object.values(new Date(0))`)).toEqual([]) expect(await value(`return Object.entries(new Set([1]))`)).toEqual([]) expect(await value(`return Object.assign({}, new Map([["a", 1]]))`)).toEqual({}) expect(await value(`return Object.hasOwn(new Date(0), "time")`)).toBe(false) }) test("the host boundary still serializes JSON forms: results, JSON.stringify, and tool arguments", async () => { expect(await value(`return { d: new Date(0), m: new Map([["a", 1]]) }`)).toEqual({ d: "1970-01-01T00:00:00.000Z", m: {}, }) expect(await value(`return JSON.stringify({ d: new Date(0) })`)).toBe('{"d":"1970-01-01T00:00:00.000Z"}') const observed: Array = [] const capture = Tool.make({ description: "Capture the exact input the host receives", input: { type: "object" }, execute: (input) => Effect.sync(() => { observed.push(input) return "ok" }), }) const result = await Effect.runPromise( CodeMode.execute({ tools: { host: { capture } }, code: `return await tools.host.capture({ when: new Date(0), tags: new Map([["a", 1]]) })`, }), ) expect(result.ok).toBe(true) expect(observed).toStrictEqual([{ when: "1970-01-01T00:00:00.000Z", tags: {} }]) }) }) describe("Uint8Array callback methods", () => { test("map and filter return new Uint8Arrays with clamped bytes", async () => { expect( await value(` const b = new Uint8Array([1, 2, 3]) const mapped = b.map((byte) => byte * 100) const filtered = b.filter((byte) => byte > 1) mapped[0] = 9 return [ [...mapped], mapped instanceof Uint8Array, Array.isArray(mapped), [...b], [...filtered], [...b.map(() => "7")], b.map((byte) => byte, {}).length, [...new Uint8Array().map((byte) => byte)], ] `), ).toEqual([[9, 200, 44], true, false, [1, 2, 3], [2, 3], [7, 7, 7], 3, []]) }) test("find, findIndex, findLast, findLastIndex, some, every, and forEach", async () => { expect( await value(` const b = new Uint8Array([1, 2, 3]) const seen = [] b.forEach((byte, index, array) => seen.push([byte, index, array === b])) return [ b.find((byte) => byte > 1), b.find((byte) => byte > 5) === undefined, b.findIndex((byte) => byte > 1), b.findIndex((byte) => byte > 5), b.findLast((byte) => byte < 3), b.findLastIndex((byte) => byte < 3), b.findLastIndex((byte) => byte > 9), b.some((byte) => byte > 2), b.every((byte) => byte > 2), new Uint8Array().some(() => true), new Uint8Array().every(() => false), b.every((byte, index, array) => array === b), seen, ] `), ).toEqual([ 2, true, 1, -1, 2, 1, -1, true, false, false, true, true, [ [1, 0, true], [2, 1, true], [3, 2, true], ], ]) }) test("reduce and reduceRight", async () => { expect( await value(` const b = new Uint8Array([1, 2, 3]) return [ b.reduce((sum, byte) => sum + byte), b.reduce((sum, byte) => sum + byte, 10), b.reduceRight((text, byte) => text + byte, ""), new Uint8Array().reduce((sum, byte) => sum + byte, 5), b.reduce((_, byte, index, array) => array === b && index, 0), ] `), ).toEqual([6, 16, "321", 5, 2]) expect((await error(`new Uint8Array().reduce((sum, byte) => sum + byte)`)).message).toContain( "Uint8Array.reduce of an empty array with no initial value", ) expect((await error(`new Uint8Array().reduceRight((sum, byte) => sum + byte)`)).message).toContain( "Uint8Array.reduceRight of an empty array with no initial value", ) expect((await error(`new Uint8Array([1]).map(null)`)).message).toContain("Uint8Array.map expects a function") }) test("sort is numeric by default and in place, with an optional comparator", async () => { expect( await value(` const b = new Uint8Array([10, 9, 1]) const same = b.sort() === b const desc = new Uint8Array([3, 1, 2]).sort((x, y) => y - x) return [same, [...b], [...desc], desc instanceof Uint8Array, [...new Uint8Array([2, 1]).sort(() => NaN)]] `), ).toEqual([true, [1, 9, 10], [3, 2, 1], true, [2, 1]]) expect((await error(`new Uint8Array([2, 1]).sort(null)`)).message).toContain("Uint8Array.sort expects a function") }) test("lastIndexOf treats an explicit undefined fromIndex as 0", async () => { expect( await value(` const a = [1, 2, 1] const b = new Uint8Array([1, 2, 1]) return [a.lastIndexOf(1, undefined), a.lastIndexOf(1), a.lastIndexOf(2, undefined), b.lastIndexOf(1, undefined), b.lastIndexOf(1)] `), ).toEqual([0, 2, -1, 0, 2]) }) })