From ec8eea7cbece64dc301ce93bc201016cc9ca08d0 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 8 Jul 2026 17:26:11 -0500 Subject: [PATCH] test(codemode): add Test262 array coverage (#35965) --- .../test/array-callbacks-test262.test.ts | 325 ++++++++++++++++++ .../codemode/test/array-core-test262.test.ts | 323 +++++++++++++++++ packages/codemode/test/parity.test.ts | 96 +----- packages/codemode/test/stdlib.test.ts | 21 +- packages/codemode/test/test262-array.md | 77 +++++ 5 files changed, 736 insertions(+), 106 deletions(-) create mode 100644 packages/codemode/test/array-callbacks-test262.test.ts create mode 100644 packages/codemode/test/array-core-test262.test.ts create mode 100644 packages/codemode/test/test262-array.md diff --git a/packages/codemode/test/array-callbacks-test262.test.ts b/packages/codemode/test/array-callbacks-test262.test.ts new file mode 100644 index 00000000000..3de3a94a4cb --- /dev/null +++ b/packages/codemode/test/array-callbacks-test262.test.ts @@ -0,0 +1,325 @@ +/* + * Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75: + * - test/built-ins/Array/prototype/map/15.4.4.19-8-1.js + * - test/built-ins/Array/prototype/map/15.4.4.19-8-2.js + * - test/built-ins/Array/prototype/map/15.4.4.19-8-b-1.js + * - test/built-ins/Array/prototype/filter/15.4.4.20-9-1.js + * - test/built-ins/Array/prototype/filter/15.4.4.20-9-2.js + * - test/built-ins/Array/prototype/filter/15.4.4.20-9-b-1.js + * - test/built-ins/Array/prototype/find/predicate-call-parameters.js + * - test/built-ins/Array/prototype/find/predicate-not-called-on-empty-array.js + * - test/built-ins/Array/prototype/find/return-found-value-predicate-result-is-true.js + * - test/built-ins/Array/prototype/find/return-undefined-if-predicate-returns-false-value.js + * - test/built-ins/Array/prototype/findIndex/predicate-call-parameters.js + * - test/built-ins/Array/prototype/findIndex/return-index-predicate-result-is-true.js + * - test/built-ins/Array/prototype/findIndex/return-negative-one-if-predicate-returns-false-value.js + * - test/built-ins/Array/prototype/findLast/predicate-call-parameters.js + * - test/built-ins/Array/prototype/findLast/return-found-value-predicate-result-is-true.js + * - test/built-ins/Array/prototype/findLast/return-undefined-if-predicate-returns-false-value.js + * - test/built-ins/Array/prototype/findLastIndex/predicate-call-parameters.js + * - test/built-ins/Array/prototype/findLastIndex/return-index-predicate-result-is-true.js + * - test/built-ins/Array/prototype/findLastIndex/return-negative-one-if-predicate-returns-false-value.js + * - test/built-ins/Array/prototype/some/15.4.4.17-7-1.js + * - test/built-ins/Array/prototype/some/15.4.4.17-8-1.js + * - test/built-ins/Array/prototype/every/15.4.4.16-7-1.js + * - test/built-ins/Array/prototype/every/15.4.4.16-8-1.js + * - test/built-ins/Array/prototype/forEach/15.4.4.18-7-1.js + * - test/built-ins/Array/prototype/forEach/15.4.4.18-7-2.js + * - test/built-ins/Array/prototype/reduce/15.4.4.21-9-5.js + * - test/built-ins/Array/prototype/reduce/15.4.4.21-9-1.js + * - test/built-ins/Array/prototype/reduce/15.4.4.21-10-1.js + * - test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-5.js + * - test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-1.js + * - test/built-ins/Array/prototype/reduceRight/15.4.4.22-10-1.js + * - test/built-ins/Array/prototype/flatMap/depth-always-one.js + * - test/built-ins/Array/prototype/flatMap/mapperfunction-throws.js + * - test/built-ins/Array/prototype/sort/S15.4.4.11_A1.1_T1.js + * - test/built-ins/Array/prototype/sort/S15.4.4.11_A2.1_T1.js + * - test/built-ins/Array/prototype/sort/stability-5-elements.js + * - test/built-ins/Array/prototype/toSorted/comparefn-controls-sort.js + * - test/built-ins/Array/prototype/toSorted/comparefn-default.js + * - test/built-ins/Array/prototype/toSorted/immutable.js + * - test/built-ins/Array/prototype/toSorted/zero-or-one-element.js + * + * Copyright (C) 2015 the V8 project authors. All rights reserved. + * Copyright (C) 2018 Mathias Bynens. All rights reserved. + * Copyright (C) 2018 Shilpi Jain and Michael Ficarra. All rights reserved. + * Copyright (C) 2021 Igalia, S.L. All rights reserved. + * Copyright (C) 2021 Microsoft. All rights reserved. + * Copyright (C) 2025 Google. All rights reserved. + * Copyright (C) 2026 Garham Lee. All rights reserved. + * Copyright (c) 2012 Ecma International. 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 } 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 +} + +const cases = [ + { + path: "test/built-ins/Array/prototype/map/15.4.4.19-8-1.js", + code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const result = input.map((value) => { input[2] = 3; input[5] = 6; return 1 }); return [result.length, result[5] === undefined]`, + expected: [5, true], + }, + { + path: "test/built-ins/Array/prototype/map/15.4.4.19-8-2.js", + code: `const input = [1, 2, 3, 4, 5]; const result = input.map((value) => { input[4] = -1; return value > 0 ? 1 : 0 }); return [result.length, result[4]]`, + expected: [5, 0], + }, + { + path: "test/built-ins/Array/prototype/map/15.4.4.19-8-b-1.js", + code: `const input = []; input[10] = 0; input.pop(); input[1] = undefined; let calls = 0; const result = input.map(() => { calls += 1; return 1 }); return [result.length, calls, 0 in result, 1 in result]`, + expected: [10, 1, false, true], + }, + { + path: "test/built-ins/Array/prototype/filter/15.4.4.20-9-1.js", + code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const result = input.filter(() => { input[2] = 3; input[5] = 6; return true }); return result`, + expected: [1, 2, 3, 4, 5], + }, + { + path: "test/built-ins/Array/prototype/filter/15.4.4.20-9-2.js", + code: `const input = [1, 2, 3, 4, 5]; return input.filter((value) => { input[2] = -1; input[4] = -1; return value > 0 })`, + expected: [1, 2, 4], + }, + { + path: "test/built-ins/Array/prototype/filter/15.4.4.20-9-b-1.js", + code: `const input = []; input[9] = 0; input.pop(); input[1] = undefined; let calls = 0; const result = input.filter(() => { calls += 1; return false }); return [result, calls]`, + expected: [[], 1], + }, + { + path: "test/built-ins/Array/prototype/find/predicate-call-parameters.js", + code: `const input = [10, 20]; const seen = []; input.find((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`, + expected: [ + [10, 0, true], + [20, 1, true], + ], + }, + { + path: "test/built-ins/Array/prototype/find/return-found-value-predicate-result-is-true.js", + code: `return [1, 2, 3].find((value) => value > 1)`, + expected: 2, + }, + { + path: "test/built-ins/Array/prototype/find/return-undefined-if-predicate-returns-false-value.js", + code: `return [1, 2, 3].find((value) => value > 4) === undefined`, + expected: true, + }, + { + path: "test/built-ins/Array/prototype/find/predicate-not-called-on-empty-array.js", + code: `let calls = 0; const result = [].find(() => { calls += 1; return true }); return [result === undefined, calls]`, + expected: [true, 0], + }, + { + path: "test/built-ins/Array/prototype/findIndex/predicate-call-parameters.js", + code: `const input = [10, 20]; const seen = []; input.findIndex((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`, + expected: [ + [10, 0, true], + [20, 1, true], + ], + }, + { + path: "test/built-ins/Array/prototype/findIndex/return-index-predicate-result-is-true.js", + code: `return [1, 2, 3].findIndex((value) => value > 1)`, + expected: 1, + }, + { + path: "test/built-ins/Array/prototype/findIndex/return-negative-one-if-predicate-returns-false-value.js", + code: `return [1, 2, 3].findIndex((value) => value > 4)`, + expected: -1, + }, + { + path: "test/built-ins/Array/prototype/findLast/predicate-call-parameters.js", + code: `const input = [10, 20]; const seen = []; input.findLast((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`, + expected: [ + [20, 1, true], + [10, 0, true], + ], + }, + { + path: "test/built-ins/Array/prototype/findLast/return-found-value-predicate-result-is-true.js", + code: `return [1, 2, 3].findLast((value) => value < 3)`, + expected: 2, + }, + { + path: "test/built-ins/Array/prototype/findLast/return-undefined-if-predicate-returns-false-value.js", + code: `return [1, 2, 3].findLast((value) => value > 4) === undefined`, + expected: true, + }, + { + path: "test/built-ins/Array/prototype/findLastIndex/predicate-call-parameters.js", + code: `const input = [10, 20]; const seen = []; input.findLastIndex((value, index, receiver) => { seen.push([value, index, receiver === input]); return false }); return seen`, + expected: [ + [20, 1, true], + [10, 0, true], + ], + }, + { + path: "test/built-ins/Array/prototype/findLastIndex/return-index-predicate-result-is-true.js", + code: `return [1, 2, 3].findLastIndex((value) => value < 3)`, + expected: 1, + }, + { + path: "test/built-ins/Array/prototype/findLastIndex/return-negative-one-if-predicate-returns-false-value.js", + code: `return [1, 2, 3].findLastIndex((value) => value > 4)`, + expected: -1, + }, + { + path: "test/built-ins/Array/prototype/some/15.4.4.17-7-1.js", + code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const seen = []; const result = input.some((value) => { input[2] = 3; seen.push(value); return false }); return [result, seen.includes(3)]`, + expected: [false, true], + }, + { + path: "test/built-ins/Array/prototype/some/15.4.4.17-8-1.js", + code: `return [].some(() => true)`, + expected: false, + }, + { + path: "test/built-ins/Array/prototype/every/15.4.4.16-7-1.js", + code: `const input = [1, 2]; input[3] = 4; input[4] = 5; const seen = []; const result = input.every((value) => { input[2] = 3; seen.push(value); return true }); return [result, seen.includes(3)]`, + expected: [true, true], + }, + { + path: "test/built-ins/Array/prototype/every/15.4.4.16-8-1.js", + code: `return [].every(() => false)`, + expected: true, + }, + { + path: "test/built-ins/Array/prototype/forEach/15.4.4.18-7-1.js", + code: `const input = [1, 2]; input[3] = 4; input[4] = 5; let calls = 0; input.forEach(() => { calls += 1; input[2] = 3; input[5] = 6 }); return calls`, + expected: 5, + }, + { + path: "test/built-ins/Array/prototype/forEach/15.4.4.18-7-2.js", + code: `const input = [1, 2, 3]; const seen = []; input.forEach((value, index) => { seen.push(value); if (index === 0) input.pop() }); return seen`, + expected: [1, 2], + }, + { + path: "test/built-ins/Array/prototype/reduce/15.4.4.21-9-1.js", + code: `const input = [1, 2]; input[3] = 4; input[4] = "5"; return input.reduce((accumulator, value) => { input[2] = 3; input[5] = 6; return accumulator + value })`, + expected: "105", + }, + { + path: "test/built-ins/Array/prototype/reduce/15.4.4.21-9-5.js", + code: `let calls = 0; const result = [1].reduce(() => { calls += 1; return 2 }); return [result, calls]`, + expected: [1, 0], + }, + { + path: "test/built-ins/Array/prototype/reduce/15.4.4.21-10-1.js", + code: `const input = [1, 2, 3, 4, 5]; input.reduce(() => 1); return input`, + expected: [1, 2, 3, 4, 5], + }, + { + path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-1.js", + code: `const input = ["1", 2]; input[3] = 4; input[4] = "5"; return input.reduceRight((accumulator, value) => { input[2] = 3; input[5] = 6; return accumulator + value })`, + expected: "54321", + }, + { + path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-9-5.js", + code: `let calls = 0; const result = [1].reduceRight(() => { calls += 1; return 2 }); return [result, calls]`, + expected: [1, 0], + }, + { + path: "test/built-ins/Array/prototype/reduceRight/15.4.4.22-10-1.js", + code: `const input = [1, 2, 3, 4, 5]; input.reduceRight(() => 1); return input`, + expected: [1, 2, 3, 4, 5], + }, + { + path: "test/built-ins/Array/prototype/flatMap/depth-always-one.js", + code: `return [1, 2, 3].flatMap((value) => [[value * 2]])`, + expected: [[2], [4], [6]], + }, + { + path: "test/built-ins/Array/prototype/flatMap/mapperfunction-throws.js", + code: `try { [1, 2].flatMap(() => { throw "stop" }) } catch (error) { return error === "stop" } return false`, + expected: true, + }, + { + path: "test/built-ins/Array/prototype/sort/S15.4.4.11_A1.1_T1.js", + code: `const input = []; input[2] = 0; input.pop(); input.sort(); return [input.length, input[0] === undefined, input[1] === undefined]`, + expected: [2, true, true], + }, + { + path: "test/built-ins/Array/prototype/sort/S15.4.4.11_A2.1_T1.js", + code: `return ["z", "y", "x", "w", "v", "u", "t", "s", "r", "q", "p", "o", "n", "M", "L", "K", "J", "I", "H", "G", "F", "E", "D", "C", "B", "A"].sort()`, + expected: [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + ], + }, + { + path: "test/built-ins/Array/prototype/sort/stability-5-elements.js", + code: `const input = [{ n: "A", r: 2 }, { n: "B", r: 3 }, { n: "C", r: 2 }, { n: "D", r: 3 }, { n: "E", r: 3 }]; return input.sort((left, right) => right.r - left.r).map((item) => item.n).join("")`, + expected: "BDEAC", + }, + { + path: "test/built-ins/Array/prototype/toSorted/comparefn-controls-sort.js", + code: `const mixed = [333, 33, 3, 222, 22, 2, 111, 11, 1]; return [[1, 2, 3, 4].toSorted((a, b) => a - b), [4, 3, 2, 1].toSorted((a, b) => a - b), mixed.toSorted((a, b) => a - b), [1, 2, 3, 4].toSorted((a, b) => b - a), [4, 3, 2, 1].toSorted((a, b) => b - a), mixed.toSorted((a, b) => b - a)]`, + expected: [ + [1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 11, 22, 33, 111, 222, 333], + [4, 3, 2, 1], + [4, 3, 2, 1], + [333, 222, 111, 33, 22, 11, 3, 2, 1], + ], + }, + { + path: "test/built-ins/Array/prototype/toSorted/comparefn-default.js", + code: `return [[1, 2, 3, 4].toSorted(), [4, 3, 2, 1].toSorted(), ["a", 2, 1, "z"].toSorted(), [333, 33, 3, 222, 22, 2, 111, 11, 1].toSorted()]`, + expected: [ + [1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, "a", "z"], + [1, 11, 111, 2, 22, 222, 3, 33, 333], + ], + }, + { + path: "test/built-ins/Array/prototype/toSorted/immutable.js", + code: `const input = [2, 0, 1]; const result = input.toSorted(); return [input, result !== input]`, + expected: [[2, 0, 1], true], + }, + { + path: "test/built-ins/Array/prototype/toSorted/zero-or-one-element.js", + code: `const zero = []; const one = [1]; const zeroResult = zero.toSorted(); const oneResult = one.toSorted(); return [zeroResult, oneResult, zeroResult !== zero, oneResult !== one]`, + expected: [[], [1], true, true], + }, +] as const + +describe("Test262 Array callback adaptations", () => { + for (const item of cases) { + test(item.path, async () => { + expect(await value(item.code)).toEqual(item.expected) + }) + } +}) diff --git a/packages/codemode/test/array-core-test262.test.ts b/packages/codemode/test/array-core-test262.test.ts new file mode 100644 index 00000000000..a04e1c2566c --- /dev/null +++ b/packages/codemode/test/array-core-test262.test.ts @@ -0,0 +1,323 @@ +/* + * Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75: + * - test/built-ins/Array/prototype/includes/samevaluezero.js + * - test/built-ins/Array/prototype/includes/using-fromindex.js + * - test/built-ins/Array/prototype/join/S15.4.4.5_A3.1_T1.js + * - test/built-ins/Array/prototype/join/S15.4.4.5_A3.2_T1.js + * - test/built-ins/Array/prototype/slice/S15.4.4.10_A1.2_T2.js + * - test/built-ins/Array/prototype/concat/S15.4.4.4_A1_T1.js + * - test/built-ins/Array/prototype/concat/S15.4.4.4_A1_T2.js + * - test/built-ins/Array/prototype/concat/S15.4.4.4_A1_T3.js + * - test/built-ins/Array/prototype/indexOf/fromindex-zero-conversion.js + * - test/built-ins/Array/prototype/indexOf/length-zero-returns-minus-one.js + * - test/built-ins/Array/prototype/lastIndexOf/fromindex-zero-conversion.js + * - test/built-ins/Array/prototype/lastIndexOf/length-zero-returns-minus-one.js + * - test/built-ins/Array/prototype/at/returns-item.js + * - test/built-ins/Array/prototype/at/returns-item-relative-index.js + * - test/built-ins/Array/prototype/at/returns-undefined-for-out-of-range-index.js + * - test/built-ins/Array/prototype/flat/null-undefined-elements.js + * - test/built-ins/Array/prototype/flat/positive-infinity.js + * - test/built-ins/Array/prototype/reverse/S15.4.4.8_A1_T1.js + * - test/built-ins/Array/prototype/toReversed/immutable.js + * - test/built-ins/Array/prototype/toReversed/zero-or-one-element.js + * - test/built-ins/Array/prototype/with/immutable.js + * - test/built-ins/Array/prototype/with/index-negative.js + * - test/built-ins/Array/prototype/push/S15.4.4.7_A1_T1.js + * - test/built-ins/Array/prototype/pop/S15.4.4.6_A1.1_T1.js + * - test/built-ins/Array/prototype/shift/S15.4.4.9_A1.1_T1.js + * - test/built-ins/Array/prototype/unshift/S15.4.4.13_A1_T1.js + * - test/built-ins/Array/prototype/splice/S15.4.4.12_A1.1_T1.js + * - test/built-ins/Array/prototype/splice/S15.4.4.12_A1.2_T1.js + * - test/built-ins/Array/prototype/splice/called_with_one_argument.js + * - test/built-ins/Array/prototype/fill/fill-values.js + * - test/built-ins/Array/prototype/fill/fill-values-custom-start-and-end.js + * - test/built-ins/Array/prototype/fill/return-this.js + * - test/built-ins/Array/prototype/copyWithin/non-negative-target-start-and-end.js + * - test/built-ins/Array/prototype/copyWithin/return-this.js + * - test/built-ins/Array/prototype/keys/iteration.js + * - test/built-ins/Array/prototype/values/iteration.js + * - test/built-ins/Array/prototype/entries/iteration.js + * - test/built-ins/Array/isArray/15.4.3.2-0-3.js + * - test/built-ins/Array/isArray/15.4.3.2-0-4.js + * - test/built-ins/Array/from/from-array.js + * - test/built-ins/Array/from/from-string.js + * - test/built-ins/Array/from/array-like-has-length-but-no-indexes-with-values.js + * - test/built-ins/Array/of/creates-a-new-array-from-arguments.js + * + * Copyright (C) 2015 André Bargull. All rights reserved. + * Copyright (C) 2015 the V8 project authors. All rights reserved. + * Copyright (C) 2016 the V8 project authors. All rights reserved. + * Copyright (C) 2018 Shilpi Jain and Michael Ficarra. All rights reserved. + * Copyright (C) 2020 Alexey Shvayka. All rights reserved. + * Copyright (C) 2020 Rick Waldron. All rights reserved. + * Copyright (C) 2021 Igalia, S.L. All rights reserved. + * Copyright (c) 2012 Ecma International. All rights reserved. + * Copyright (c) 2014 Hank Yates. All rights reserved. + * Copyright (c) 2015 the V8 project authors. All rights reserved. + * Copyright (c) 2021 Rick Waldron. All rights reserved. + * Copyright 2009 the Sputnik authors. All rights reserved. + * Copyright 2015 Microsoft Corporation. All rights reserved. + * Copyright 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 } 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 +} + +const cases = [ + { + path: "test/built-ins/Array/prototype/includes/samevaluezero.js", + code: `const input = [42, 0, 1, NaN]; return [input.includes(42), input.includes("42"), input.includes([42]), input.includes(true), input.includes(NaN), input.includes(0), input.includes(-0), input.includes(null), input.includes("")]`, + expected: [true, false, false, false, true, true, true, false, false], + }, + { + path: "test/built-ins/Array/prototype/includes/using-fromindex.js", + code: `const input = ["a", "b", "c"]; return [input.includes("a", 0), input.includes("a", 1), input.includes("a", -4), input.includes("a", -3), input.includes("a", -2), input.includes("b", 0), input.includes("b", 1), input.includes("b", 2), input.includes("b", -3), input.includes("b", -2), input.includes("b", -1), input.includes("c", 0), input.includes("c", 2), input.includes("c", 3), input.includes("c", -3), input.includes("c", -1)]`, + expected: [true, false, true, true, false, true, true, false, true, true, false, true, true, false, true, true], + }, + { + path: "test/built-ins/Array/prototype/join/S15.4.4.5_A3.1_T1.js", + code: `return [[0, 1, 2, 3].join("&"), [0, 1, 2, 3].join("")]`, + expected: ["0&1&2&3", "0123"], + }, + { + path: "test/built-ins/Array/prototype/join/S15.4.4.5_A3.2_T1.js", + code: `return [ + ["", "", ""].join(""), + ["&", "&", "&"].join("&"), + [true, true, true].join(), + [null, null, null].join(), + [undefined, undefined, undefined].join(), + [Infinity, Infinity, Infinity].join(), + [NaN, NaN, NaN].join(), + ]`, + expected: ["", "&&&&&", "true,true,true", ",,", ",,", "Infinity,Infinity,Infinity", "NaN,NaN,NaN"], + }, + { + path: "test/built-ins/Array/prototype/slice/S15.4.4.10_A1.2_T2.js", + code: `return [0, 1, 2, 3, 4].slice(-1, 5)`, + expected: [4], + }, + { + path: "test/built-ins/Array/prototype/concat/S15.4.4.4_A1_T1.js", + code: `return [].concat([0, 1], [2, 3, 4])`, + expected: [0, 1, 2, 3, 4], + }, + { + path: "test/built-ins/Array/prototype/concat/S15.4.4.4_A1_T2.js", + code: `const object = { value: 1 }; const result = [0].concat(object, [1, 2], -1, true, "NaN"); return [result, result[1] === object]`, + expected: [[0, { value: 1 }, 1, 2, -1, true, "NaN"], true], + }, + { + path: "test/built-ins/Array/prototype/concat/S15.4.4.4_A1_T3.js", + code: `const input = [0, 1]; const result = input.concat(); return [result, result !== input]`, + expected: [[0, 1], true], + }, + { + path: "test/built-ins/Array/prototype/indexOf/fromindex-zero-conversion.js", + code: `const result = [true].indexOf(true, -0); return [result, 1 / result === Infinity]`, + expected: [0, true], + }, + { + path: "test/built-ins/Array/prototype/indexOf/length-zero-returns-minus-one.js", + code: `return [].indexOf(1)`, + expected: -1, + }, + { + path: "test/built-ins/Array/prototype/lastIndexOf/fromindex-zero-conversion.js", + code: `const result = [true].lastIndexOf(true, -0); return [result, 1 / result === Infinity]`, + expected: [0, true], + }, + { + path: "test/built-ins/Array/prototype/lastIndexOf/length-zero-returns-minus-one.js", + code: `return [].lastIndexOf(1)`, + expected: -1, + }, + { + path: "test/built-ins/Array/prototype/at/returns-item.js", + code: `const input = [1, 2, 3, 4, undefined, 5]; return [input.at(0), input.at(1), input.at(2), input.at(3), input.at(4) === undefined, input.at(5)]`, + expected: [1, 2, 3, 4, true, 5], + }, + { + path: "test/built-ins/Array/prototype/at/returns-item-relative-index.js", + code: `const input = [1, 2, 3, 4, undefined, 5]; return [input.at(0), input.at(-1), input.at(-2) === undefined, input.at(-3), input.at(-4)]`, + expected: [1, 5, true, 4, 3], + }, + { + path: "test/built-ins/Array/prototype/at/returns-undefined-for-out-of-range-index.js", + code: `const input = []; return [input.at(-2) === undefined, input.at(0) === undefined, input.at(1) === undefined]`, + expected: [true, true, true], + }, + { + path: "test/built-ins/Array/prototype/flat/null-undefined-elements.js", + code: `const result = [1, [null, [undefined]]].flat(2); return [result.length, result[0], result[1] === null, result[2] === undefined]`, + expected: [3, 1, true, true], + }, + { + path: "test/built-ins/Array/prototype/flat/positive-infinity.js", + code: `return [1, [2, [3, [4]]]].flat(Infinity)`, + expected: [1, 2, 3, 4], + }, + { + path: "test/built-ins/Array/prototype/reverse/S15.4.4.8_A1_T1.js", + code: `const empty = []; const one = [1]; const input = [1, 2]; const emptyResult = empty.reverse(); const oneResult = one.reverse(); const result = input.reverse(); return [emptyResult === empty, oneResult === one, result === input, input]`, + expected: [true, true, true, [2, 1]], + }, + { + path: "test/built-ins/Array/prototype/toReversed/immutable.js", + code: `const input = [0, 1, 2]; const result = input.toReversed(); return [input, result !== input]`, + expected: [[0, 1, 2], true], + }, + { + path: "test/built-ins/Array/prototype/toReversed/zero-or-one-element.js", + code: `const zero = []; const one = [1]; const zeroResult = zero.toReversed(); const oneResult = one.toReversed(); return [zeroResult, oneResult, zeroResult !== zero, oneResult !== one]`, + expected: [[], [1], true, true], + }, + { + path: "test/built-ins/Array/prototype/with/immutable.js", + code: `const input = [0, 1, 2]; const result = input.with(1, 3); return [input, result !== input, input.with(1, 1) !== input]`, + expected: [[0, 1, 2], true, true], + }, + { + path: "test/built-ins/Array/prototype/with/index-negative.js", + code: `const input = [0, 1, 2]; return [input.with(-1, 4), input.with(-3, 4)]`, + expected: [ + [0, 1, 4], + [4, 1, 2], + ], + }, + { + path: "test/built-ins/Array/prototype/push/S15.4.4.7_A1_T1.js", + code: `const input = []; return [input.push(1), input.push(), input.push(-1), input]`, + expected: [1, 1, 2, [1, -1]], + }, + { + path: "test/built-ins/Array/prototype/pop/S15.4.4.6_A1.1_T1.js", + code: `const input = []; return [input.pop() === undefined, input.length]`, + expected: [true, 0], + }, + { + path: "test/built-ins/Array/prototype/shift/S15.4.4.9_A1.1_T1.js", + code: `const input = []; return [input.shift() === undefined, input.length]`, + expected: [true, 0], + }, + { + path: "test/built-ins/Array/prototype/unshift/S15.4.4.13_A1_T1.js", + code: `const input = []; return [input.unshift(1), input[0], input.unshift(), input.unshift(-1), input]`, + expected: [1, 1, 1, 2, [-1, 1]], + }, + { + path: "test/built-ins/Array/prototype/splice/S15.4.4.12_A1.1_T1.js", + code: `const input = [0, 1, 2, 3]; const removed = input.splice(0, 3); return [input, removed]`, + expected: [[3], [0, 1, 2]], + }, + { + path: "test/built-ins/Array/prototype/splice/S15.4.4.12_A1.2_T1.js", + code: `const input = [0, 1]; const removed = input.splice(-2, -1); return [input, removed]`, + expected: [[0, 1], []], + }, + { + path: "test/built-ins/Array/prototype/splice/called_with_one_argument.js", + code: `const input = ["first", "second", "third"]; const removed = input.splice(1); return [input, removed]`, + expected: [["first"], ["second", "third"]], + }, + { + path: "test/built-ins/Array/prototype/fill/fill-values-custom-start-and-end.js", + code: `const input = [0, 0, 0, 0, 0]; input.fill(8, -3, 4); const sparse = []; sparse[4] = 0; sparse.fill(8, 1, 3); return [[0, 0, 0].fill(8, 1, 2), input, [0, 0, 0, 0, 0].fill(8, -2, -1), [0, 0, 0, 0, 0].fill(8, -1, -3), [0 in sparse, sparse[1], sparse[2], 3 in sparse, sparse[4]]]`, + expected: [ + [0, 8, 0], + [0, 0, 8, 8, 0], + [0, 0, 0, 8, 0], + [0, 0, 0, 0, 0], + [false, 8, 8, false, 0], + ], + }, + { + path: "test/built-ins/Array/prototype/fill/return-this.js", + code: `const input = []; return input.fill(1) === input`, + expected: true, + }, + { + path: "test/built-ins/Array/prototype/fill/fill-values.js", + code: `const omitted = [0, 0].fill(); return [[].fill(8), omitted.map((value) => value === undefined), [0, 0, 0].fill(8)]`, + expected: [[], [true, true], [8, 8, 8]], + }, + { + path: "test/built-ins/Array/prototype/copyWithin/non-negative-target-start-and-end.js", + code: `return [[0, 1, 2, 3].copyWithin(0, 0, 0), [0, 1, 2, 3].copyWithin(0, 0, 2), [0, 1, 2, 3].copyWithin(0, 1, 2), [0, 1, 2, 3].copyWithin(1, 0, 2), [0, 1, 2, 3, 4, 5].copyWithin(1, 3, 5)]`, + expected: [ + [0, 1, 2, 3], + [0, 1, 2, 3], + [1, 1, 2, 3], + [0, 0, 1, 3], + [0, 3, 4, 3, 4, 5], + ], + }, + { + path: "test/built-ins/Array/prototype/copyWithin/return-this.js", + code: `const input = [0, 1, 2, 3]; const result = input.copyWithin(1, 0, 2); return [input, result === input]`, + expected: [[0, 0, 1, 3], true], + }, + { + path: "test/built-ins/Array/prototype/keys/iteration.js", + code: `return ["a", "b", "c"].keys()`, + expected: [0, 1, 2], + }, + { + path: "test/built-ins/Array/prototype/values/iteration.js", + code: `return ["a", "b", "c"].values()`, + expected: ["a", "b", "c"], + }, + { + path: "test/built-ins/Array/prototype/entries/iteration.js", + code: `return ["a", "b"].entries()`, + expected: [ + [0, "a"], + [1, "b"], + ], + }, + { + path: "test/built-ins/Array/isArray/15.4.3.2-0-3.js", + code: `return [Array.isArray([]), Array.isArray([1]), Array.isArray(Array.of(1))]`, + expected: [true, true, true], + }, + { + path: "test/built-ins/Array/isArray/15.4.3.2-0-4.js", + code: `return [Array.isArray(42), Array.isArray({}), Array.isArray(null), Array.isArray("array")]`, + expected: [false, false, false, false], + }, + { + path: "test/built-ins/Array/from/from-array.js", + code: `const input = [0, "foo", undefined, Infinity]; const result = Array.from(input); return [result.length, result[0], result[1], result[2] === undefined, result[3] === Infinity, result !== input, result instanceof Array]`, + expected: [4, 0, "foo", true, true, true, true], + }, + { + path: "test/built-ins/Array/from/from-string.js", + code: `return Array.from("Test")`, + expected: ["T", "e", "s", "t"], + }, + { + path: "test/built-ins/Array/from/array-like-has-length-but-no-indexes-with-values.js", + code: `const result = Array.from({ length: 5 }); const mapped = result.map(() => 1); return [result.length, result.map((value) => value === undefined), mapped.length, mapped]`, + expected: [5, [true, true, true, true, true], 5, [1, 1, 1, 1, 1]], + }, + { + path: "test/built-ins/Array/of/creates-a-new-array-from-arguments.js", + code: `const mixed = Array.of(undefined, false, null, undefined); return [Array.of("Mike", "Rick", "Leo"), mixed.length, mixed[0] === undefined, mixed[1], mixed[2], mixed[3] === undefined, Array.of()]`, + expected: [["Mike", "Rick", "Leo"], 4, true, false, null, true, []], + }, +] as const + +describe("Test262 Array core adaptations", () => { + for (const item of cases) { + test(item.path, async () => { + expect(await value(item.code)).toEqual(item.expected) + }) + } +}) diff --git a/packages/codemode/test/parity.test.ts b/packages/codemode/test/parity.test.ts index 5593831127a..c8046a69671 100644 --- a/packages/codemode/test/parity.test.ts +++ b/packages/codemode/test/parity.test.ts @@ -42,11 +42,6 @@ describe("H2: string property access reads as undefined (not a throw)", () => { test("unknown property on a number is undefined", async () => { expect(await value(`return (5).foo ?? "n"`)).toBe("n") }) - - test("supported string methods still work", async () => { - expect(await value(`return "AB".toLowerCase()`)).toBe("ab") - expect(await value(`return "hello".length`)).toBe(5) - }) }) describe("H3: array property access reads as undefined (not a throw)", () => { @@ -63,8 +58,7 @@ describe("H3: array property access reads as undefined (not a throw)", () => { expect(await value(`return [1,2,3].toSpliced === undefined`)).toBe(true) }) - test("supported array methods and indexing still work", async () => { - expect(await value(`return [1,2,3].map(x => x + 1)`)).toEqual([2, 3, 4]) + test("array indexing still works", async () => { expect(await value(`return [1,2,3][9] === undefined`)).toBe(true) expect(await value(`return [1,2,3][9]`)).toBeNull() }) @@ -202,9 +196,6 @@ describe("Error values and instanceof", () => { "TypeError", true, ]) - expect(await value(`try { "a".normalize("NOPE") } catch (e) { return [e.name, e instanceof RangeError] }`)).toEqual( - ["RangeError", true], - ) expect(await value(`try { "a".match("(") } catch (e) { return [e.name, e instanceof SyntaxError] }`)).toEqual([ "SyntaxError", true, @@ -263,55 +254,18 @@ describe("Error values and instanceof", () => { }) }) -describe("array methods: splice, fill, copyWithin, keys/values/entries", () => { - test("sort and reverse mutate and return the receiver", async () => { +describe("CodeMode-specific array behavior", () => { + test("sort with a comparator mutates and returns the receiver", async () => { expect( await value(` - const sorted = [3, 1, 2] - const sortResult = sorted.sort((a, b) => a - b) - const reversed = [1, 2, 3] - const reverseResult = reversed.reverse() - return { sorted, sameSort: sorted === sortResult, reversed, sameReverse: reversed === reverseResult } + const input = [3, 1, 2] + const result = input.sort((a, b) => a - b) + return { input, same: input === result } `), - ).toEqual({ sorted: [1, 2, 3], sameSort: true, reversed: [3, 2, 1], sameReverse: true }) + ).toEqual({ input: [1, 2, 3], same: true }) }) - test("array callbacks receive the receiver and observe later mutations", async () => { - expect( - await value(` - const values = [1, 2, 3] - const seen = values.map((value, index, receiver) => { - if (index === 0) values[1] = 9 - return [value, receiver === values] - }) - return seen - `), - ).toEqual([ - [1, true], - [9, true], - [3, true], - ]) - expect( - await value(` - const values = [1, 2, 3] - const seen = [] - values.forEach((value, index) => { - seen.push(value) - if (index === 0) values.pop() - }) - return seen - `), - ).toEqual([1, 2]) - }) - - test("splice removes in place and returns the removed elements", async () => { - expect(await value(`const a = [1,2,3,4]; const removed = a.splice(1, 2); return { removed, a }`)).toEqual({ - removed: [2, 3], - a: [1, 4], - }) - }) - - test("splice inserts new elements at the cut", async () => { + test("splice can replace and insert elements", async () => { expect(await value(`const a = ["a","d"]; a.splice(1, 0, "b", "c"); return a`)).toEqual(["a", "b", "c", "d"]) expect(await value(`const a = [1,2,3]; const removed = a.splice(1, 1, "x"); return { removed, a }`)).toEqual({ removed: [2], @@ -319,32 +273,12 @@ describe("array methods: splice, fill, copyWithin, keys/values/entries", () => { }) }) - test("splice with one argument removes to the end; negative start counts back", async () => { - expect(await value(`const a = [1,2,3]; const removed = a.splice(1); return { removed, a }`)).toEqual({ - removed: [2, 3], - a: [1], - }) - expect(await value(`const a = [1,2,3]; const removed = a.splice(-1); return { removed, a }`)).toEqual({ - removed: [3], - a: [1, 2], - }) - }) - test("splice rejects inserting a container into itself", async () => { const err = await error(`const a = [1]; a.splice(0, 0, [a]); return a`) expect(err.kind).toBe("InvalidDataValue") expect(err.message).toContain("circular") }) - test("fill overwrites a range and returns the mutated array", async () => { - expect(await value(`const a = [1,2,3,4]; return a.fill(0, 1, 3)`)).toEqual([1, 0, 0, 4]) - expect(await value(`return [1,2,3].fill("z")`)).toEqual(["z", "z", "z"]) - }) - - test("copyWithin copies a range in place", async () => { - expect(await value(`return [1,2,3,4,5].copyWithin(0, 3)`)).toEqual([4, 5, 3, 4, 5]) - }) - test("keys/values/entries return arrays usable with for...of and spread", async () => { expect(await value(`return [...["x","y","z"].keys()]`)).toEqual([0, 1, 2]) expect(await value(`return ["x","y"].values()`)).toEqual(["x", "y"]) @@ -359,16 +293,9 @@ describe("array methods: splice, fill, copyWithin, keys/values/entries", () => { }) }) -describe("string methods: localeCompare, normalize, trim aliases", () => { +describe("CodeMode-specific string behavior", () => { test("localeCompare orders strings for sorting", async () => { expect(await value(`return ["b","a","c"].sort((x, y) => x.localeCompare(y))`)).toEqual(["a", "b", "c"]) - expect(await value(`return "a".localeCompare("a")`)).toBe(0) - }) - - test("normalize applies unicode normalization forms", async () => { - expect(await value(`return "\\u0065\\u0301".normalize("NFC").length`)).toBe(1) - expect(await value(`return "\\u00e9".normalize("NFD").length`)).toBe(2) - expect(await value(`return "x".normalize() === "x"`)).toBe(true) }) test("an invalid normalize form is a clear catchable error", async () => { @@ -453,11 +380,6 @@ describe("H5: builtin coercion functions work as array callbacks", () => { expect(await value(`return [1, 2, 3].map(String)`)).toEqual(["1", "2", "3"]) }) - test("arrow callbacks still work (no regression)", async () => { - expect(await value(`return [1, 2, 3, 4].filter(x => x % 2 === 0)`)).toEqual([2, 4]) - expect(await value(`return [1, 2, 3].reduce((a, b) => a + b, 0)`)).toBe(6) - }) - test("a non-callable callback is still rejected", async () => { const err = await error(`return [1,2,3].map(42)`) expect(err.message).toContain("callback") diff --git a/packages/codemode/test/stdlib.test.ts b/packages/codemode/test/stdlib.test.ts index d5870dab8de..dc9710bd2fc 100644 --- a/packages/codemode/test/stdlib.test.ts +++ b/packages/codemode/test/stdlib.test.ts @@ -154,9 +154,7 @@ describe("RegExp", () => { ).toEqual(["1", "22"]) }) - test("string match: non-global carries index, global lists all matches", async () => { - expect(await value(`const m = "a1b22".match(/\\d+/); return [m[0], m.index]`)).toEqual(["1", 1]) - expect(await value(`return "a1b22".match(/\\d+/g)`)).toEqual(["1", "22"]) + test("an unmatched string pattern returns null", async () => { expect(await value(`return "abc".match(/\\d/)`)).toBeNull() }) @@ -164,13 +162,6 @@ describe("RegExp", () => { expect(await value(`return "a1b22".matchAll(/(\\d+)/g).map((m) => m[1])`)).toEqual(["1", "22"]) }) - test("replace and replaceAll with patterns and $1 substitution", async () => { - expect(await value(`return "a1b2".replace(/\\d/, "#")`)).toBe("a#b2") - expect(await value(`return "a1b2".replace(/\\d/g, "#")`)).toBe("a#b#") - expect(await value(`return "a1b2".replaceAll(/\\d/g, "#")`)).toBe("a#b#") - expect(await value(`return "hi bob".replace(/b(o)b/, "[$1]")`)).toBe("hi [o]") - }) - test("function replacers receive captures, offsets, input, and named groups", async () => { expect( await value(` @@ -236,12 +227,6 @@ describe("RegExp", () => { expect(await value(`try { "a".replaceAll(/a/, "b"); return "no" } catch { return "caught" }`)).toBe("caught") }) - test("split and search accept patterns", async () => { - expect(await value(`return "a1b22c".split(/\\d+/)`)).toEqual(["a", "b", "c"]) - expect(await value(`return "ab42".search(/\\d/)`)).toBe(2) - expect(await value(`return "ab".search(/\\d/)`)).toBe(-1) - }) - 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") @@ -650,9 +635,7 @@ describe("stdlib integration", () => { true, ) expect( - await value( - `try { Object.fromEntries(new Map([["fn", Math.max]])); return false } catch { return true }`, - ), + await value(`try { Object.fromEntries(new Map([["fn", Math.max]])); return false } catch { return true }`), ).toBe(true) }) diff --git a/packages/codemode/test/test262-array.md b/packages/codemode/test/test262-array.md new file mode 100644 index 00000000000..b8e7e07a897 --- /dev/null +++ b/packages/codemode/test/test262-array.md @@ -0,0 +1,77 @@ +# Test262 Array Coverage + +The Array tests adapt Test262 at revision `250f204f23a9249ff204be2baec29600faae7b75`. They cover CodeMode's 35 +exposed instance methods and three static methods using actual arrays, accepted argument types, deterministic behavior, +and CodeMode's materialized collection conventions. Each executable case names its exact upstream source path. +`LICENSE.test262` contains the upstream BSD terms. + +This is coverage of CodeMode's bounded Array surface, not a claim of ECMAScript or Test262 conformance. One upstream +file may contain both adapted and inapplicable assertions, so a cited source means only that the represented assertions +were adapted. + +## Inventory + +The 38 relevant upstream API directories contain 2,837 files. The executable suite adapts assertions from 83 distinct +sources. + +| API | Upstream files | Adapted sources | +| ------------------------------- | -------------: | --------------: | +| `Array.prototype.map` | 216 | 3 | +| `Array.prototype.filter` | 242 | 3 | +| `Array.prototype.find` | 23 | 4 | +| `Array.prototype.findIndex` | 23 | 3 | +| `Array.prototype.findLast` | 24 | 3 | +| `Array.prototype.findLastIndex` | 24 | 3 | +| `Array.prototype.some` | 219 | 2 | +| `Array.prototype.every` | 218 | 2 | +| `Array.prototype.includes` | 30 | 2 | +| `Array.prototype.join` | 23 | 2 | +| `Array.prototype.reduce` | 260 | 3 | +| `Array.prototype.reduceRight` | 260 | 3 | +| `Array.prototype.flatMap` | 24 | 2 | +| `Array.prototype.forEach` | 190 | 2 | +| `Array.prototype.sort` | 54 | 3 | +| `Array.prototype.toSorted` | 21 | 4 | +| `Array.prototype.slice` | 71 | 1 | +| `Array.prototype.concat` | 69 | 3 | +| `Array.prototype.indexOf` | 201 | 2 | +| `Array.prototype.lastIndexOf` | 198 | 2 | +| `Array.prototype.at` | 13 | 3 | +| `Array.prototype.flat` | 19 | 2 | +| `Array.prototype.reverse` | 18 | 1 | +| `Array.prototype.toReversed` | 17 | 2 | +| `Array.prototype.with` | 21 | 2 | +| `Array.prototype.push` | 24 | 1 | +| `Array.prototype.pop` | 23 | 1 | +| `Array.prototype.shift` | 20 | 1 | +| `Array.prototype.unshift` | 22 | 1 | +| `Array.prototype.splice` | 81 | 3 | +| `Array.prototype.fill` | 22 | 3 | +| `Array.prototype.copyWithin` | 39 | 2 | +| `Array.prototype.keys` | 12 | 1 | +| `Array.prototype.values` | 12 | 1 | +| `Array.prototype.entries` | 12 | 1 | +| `Array.from` | 47 | 3 | +| `Array.isArray` | 29 | 2 | +| `Array.of` | 16 | 1 | + +## Exclusions + +Assertions are not adapted when they test behavior outside CodeMode's documented Array surface: + +- Function metadata, property descriptors, constructibility, prototype mutation, species constructors, or cross-realm + identity. +- Generic receivers, detached methods, `.call`, `.apply`, boxed values, custom coercion objects, Symbols, BigInts, + proxies, accessors, frozen arrays, typed arrays, or ArrayBuffers. +- `Array.from` mappers, custom iterables, constructor substitution, and iterator-closing behavior. +- Native iterator identity, `.next()`, completion records, or live iterator mutation. CodeMode deliberately materializes + `keys`, `values`, and `entries` as arrays. +- Sparse-array assertions that depend on literal elisions or inherited indexed properties. CodeMode's confined data + model does not preserve those prototype and hole semantics at every boundary. +- Argument coercions outside the accepted schema-like surface. Numeric positions must be numbers and `join` separators + must be strings. +- Exact native error brands where CodeMode exposes a safe runtime error instead. +- Async/effectful callbacks, circular-data rejection, sandbox-value identity, diagnostics, and host-boundary behavior. + Those remain covered by CodeMode-specific tests. + +Handwritten tests remain where they specify CodeMode behavior rather than ordinary ECMAScript Array semantics.