X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/ea0c5228a40540f4e3263f303b22f94df29abeaa..07762ac4c632a6436d43d50d58c8d91760e81e44:/collection.test.js diff --git a/collection.test.js b/collection.test.js index c066932..079ef4b 100644 --- a/collection.test.js +++ b/collection.test.js @@ -1,54 +1,1201 @@ -// ♓🌟 Piscēs ∷ collection.test.js -// ==================================================================== -// -// Copyright © 2022 Lady [@ Lady’s Computer]. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at . +// SPDX-FileCopyrightText: 2022, 2023, 2025 Lady +// SPDX-License-Identifier: MPL-2.0 +/** + * ⁌ ♓🧩 Piscēs ∷ collection.js + * + * Copyright © 2022–2023, 2025 Lady [@ Ladys Computer]. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . + */ import { assertEquals, assertSpyCall, assertSpyCalls, assertStrictEquals, + assertThrows, describe, it, spy, } from "./dev-deps.js"; -import { findIndexedEntry, isCollection } from "./collection.js"; +import { + array, + concatSpreadableCatenate, + copyWithin, + denseProxy, + fill, + filter, + findFirstIndex, + findFirstIndexedEntry, + findFirstItem, + findLastIndex, + findLastIndexedEntry, + findLastItem, + flatmap, + flatten as _flatten /* TK */, + getFirstIndex as _getFirstIndex /* TK */, + getItem as _getItem /* TK */, + getLastIndex as _getLastIndex /* TK */, + hasEvery as _hasEvery /* TK */, + hasSome as _hasSome /* TK */, + includes as _includes /* TK */, + indexedEntries as _indexedEntries /* TK */, + indices as _indices /* TK */, + isArray as _isArray /* TK */, + isCollection, + isDenseProxy, + items as _items /* TK */, + map as _map /* TK */, + pop as _pop /* TK */, + push as _push /* TK */, + reduce as _reduce /* TK */, + reverse as _reverse /* TK */, + shift as _shift /* TK */, + slice as _slice /* TK */, + sort as _sort /* TK */, + splice as _splice /* TK */, + toArray, + toDenseArray, + unshift as _unshift /* TK */, +} from "./collection.js"; + +describe("array", () => { + it("[[Call]] returns an array of the provided values", () => { + assertEquals(array("etaoin", [], true), ["etaoin", [], true]); + }); + + it("[[Call]] returns an empty array with no arguments", () => { + assertEquals(array(), []); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new array()); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(array.length, 0); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(array.name, "array"); + }); + }); +}); + +describe("concatSpreadableCatenate", () => { + it("[[Call]] catenates concat spreadable values", () => { + assertEquals( + concatSpreadableCatenate([1, 2], [2, [3]], { + length: 1, + [Symbol.isConcatSpreadable]: true, + }), + [1, 2, 2, [3], ,], + ); + }); + + it("[[Call]] does not catenate other values", () => { + assertEquals(concatSpreadableCatenate({}, "etaoin"), [ + {}, + "etaoin", + ]); + }); + + it("[[Call]] allows a nullish first argument", () => { + assertEquals(concatSpreadableCatenate(null), [null]); + assertEquals(concatSpreadableCatenate(undefined), [undefined]); + }); + + it("[[Call]] returns an empty array with no arguments", () => { + assertEquals(concatSpreadableCatenate(), []); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new concatSpreadableCatenate()); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(concatSpreadableCatenate.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + concatSpreadableCatenate.name, + "concatSpreadableCatenate", + ); + }); + }); +}); + +describe("copyWithin", () => { + it("[[Call]] copies within", () => { + assertEquals( + copyWithin(["a", "b", "c", , "e"], 0, 3, 4), + [, "b", "c", , "e"], + ); + assertEquals( + copyWithin(["a", "b", "c", , "e"], 1, 3), + ["a", , "e", , "e"], + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new copyWithin([], 0, 0)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(copyWithin.length, 3); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(copyWithin.name, "copyWithin"); + }); + }); +}); + +describe("denseProxy", () => { + const makeUnderlying = () => + Object.create({ 2: "inherited" }, { + // 0 is not present + 1: { value: "static", configurable: true, writable: true }, + // 2 is not an own property, but is present on the prototype + 3: { configurable: true, get: () => "dynamic" }, + length: { value: "4", configurable: true, writable: true }, + }); + + it("[[Call]] returns an object which inherits from the provided object", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Object.getPrototypeOf(proxy), + underlying, + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new denseProxy([])); + }); + + it("[[OwnPropertyKeys]] lists integer indices, then the length, then other keys", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + const sym = Symbol(); + proxy[sym] = "shrdlu"; + proxy["1.2.3"] = "etaion"; + assertEquals( + Reflect.ownKeys(proxy), + ["0", "1", "2", "3", "length", "1.2.3", sym], + ); + }); + + it("[[PreventExtensions]] fails if the underlying object is extensible", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.preventExtensions(proxy), + false, + ); + }); + + it("[[PreventExtensions]] fails if the underlying object has a nonconstant length", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + Object.defineProperty(underlying, "length", { get: () => 4 }); + Object.freeze(underlying); + assertStrictEquals( + Reflect.preventExtensions(proxy), + false, + ); + }); + + it("[[PreventExtensions]] succeeds if the underlying object is non·extensible and has a constant length", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + Object.defineProperty(underlying, "length", { + configurable: false, + writable: false, + }); + Object.preventExtensions(underlying); + assertStrictEquals( + Reflect.preventExtensions(proxy), + true, + ); + }); + + it("[[SetPrototypeOf]] fails to change the prototype", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.setPrototypeOf(proxy, {}), + false, + ); + }); + + it("[[SetPrototypeOf]] succeeds keeping the prototype the same", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.setPrototypeOf(proxy, underlying), + true, + ); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(denseProxy.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(denseProxy.name, "denseProxy"); + }); + }); + + describe("~[]", () => { + it("[[DefineProperty]] allows changes when the property is not an index property or length", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + const newValue = Symbol(); + proxy.etaoin = newValue; + assertStrictEquals( + proxy.etaoin, + newValue, + ); + }); + + it("[[DefineProperty]] allows changing nothing when the property is not an own property", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.defineProperty( + proxy, + "0", + { + configurable: true, + enumerable: true, + writable: false, + value: undefined, + }, + ), + true, + ); + assertStrictEquals( + Reflect.defineProperty( + proxy, + "2", + { + configurable: true, + enumerable: true, + writable: false, + value: undefined, + }, + ), + true, + ); + /* test nonconfigurable versions too */ + Object.freeze(underlying); + Object.defineProperty(proxy, "0", { configurable: false }); + Object.defineProperty(proxy, "2", { configurable: false }); + assertStrictEquals( + Reflect.defineProperty( + proxy, + "0", + Object.getOwnPropertyDescriptor(proxy, "0"), + ), + true, + ); + assertStrictEquals( + Reflect.defineProperty( + proxy, + "2", + Object.getOwnPropertyDescriptor(proxy, "2"), + ), + true, + ); + }); + + it("[[DefineProperty]] allows changing nothing when the property is a data index property", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.defineProperty( + proxy, + "1", + { + configurable: true, + enumerable: true, + writable: false, + value: underlying[1], + }, + ), + true, + ); + /* test nonconfigurable versions too */ + Object.freeze(underlying); + Object.defineProperty(proxy, "1", { configurable: false }); + assertStrictEquals( + Reflect.defineProperty( + proxy, + "1", + Object.getOwnPropertyDescriptor(proxy, "1"), + ), + true, + ); + }); + + it("[[DefineProperty]] allows changing nothing when the property is an accessor index property", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.defineProperty( + proxy, + "3", + { + configurable: true, + enumerable: true, + writable: false, + value: underlying[3], + }, + ), + true, + ); + /* test nonconfigurable versions too */ + Object.freeze(underlying); + Object.defineProperty(proxy, "1", { configurable: false }); + assertStrictEquals( + Reflect.defineProperty( + proxy, + "1", + Object.getOwnPropertyDescriptor(proxy, "1"), + ), + true, + ); + }); + + it("[[DefineProperty]] does not allow a change in enumerablility on index properties", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + for (let i = 0; i < 4; ++i) { + assertStrictEquals( + Reflect.defineProperty(proxy, i, { enumerable: false }), + false, + ); + } + }); + + it("[[DefineProperty]] does not allow a change in value on index properties", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + for (let i = 0; i < 4; ++i) { + assertStrictEquals( + Reflect.defineProperty(proxy, i, { value: "new value" }) + && (() => { + throw i; + })(), + false, + ); + } + }); + + it("[[DefineProperty]] does not allow a change in getter on index properties", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + for (let i = 0; i < 4; ++i) { + assertStrictEquals( + Reflect.defineProperty(proxy, i, { + get: () => underlying[i], + }) && (() => { + throw i; + })(), + false, + ); + } + }); + + it("[[DefineProperty]] does not allow a change in setter on index properties", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + for (let i = 0; i < 4; ++i) { + assertStrictEquals( + Reflect.defineProperty(proxy, i, { set: () => undefined }), + false, + ); + } + }); + + it("[[DefineProperty]] does not allow a change in configurability on index properties if the property in the underlying object may change", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + Object.defineProperty(underlying, "length", { + configurable: false, + writable: false, + }); + for (let i = 0; i < 4; ++i) { + assertStrictEquals( + Reflect.defineProperty(proxy, i, { configurable: false }), + false, + ); + } + }); + + it("[[DefineProperty]] does not allow a change in configurability on index properties if the length of the underlying object may change", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + Object.seal(underlying); + for (let i = 0; i < 4; ++i) { + assertStrictEquals( + Reflect.defineProperty(proxy, i, { configurable: false }), + false, + ); + } + }); + + it("[[DefineProperty]] allows a change in configurability on index properties when it is safe to do so", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + Object.defineProperty(underlying, "length", { + configurable: false, + writable: false, + }); + Object.defineProperty(underlying, "1", { + configurable: false, + writable: false, + }); + Object.defineProperty(underlying, "3", { configurable: false }); + Object.preventExtensions(underlying); + for (let i = 0; i < 4; ++i) { + assertStrictEquals( + Reflect.defineProperty(proxy, i, { configurable: false }), + true, + ); + } + }); + + it("[[Delete]] is allowed when the property is not an index property or length", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + proxy.a = "etaoin"; + assertStrictEquals( + Reflect.deleteProperty(proxy, "a"), + true, + ); + assertStrictEquals( + Reflect.has(proxy, "a"), + false, + ); + }); + + it("[[Delete]] is forbidden for index properties less than the length", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + for (let i = 0; i < 4; ++i) { + assertStrictEquals( + Reflect.deleteProperty(proxy, i), + false, + ); + } + }); + + it("[[Delete]] is allowed for index properties greater than or equal to the length", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.deleteProperty(proxy, "4"), + true, + ); + assertStrictEquals( + Reflect.deleteProperty(proxy, "5"), + true, + ); + }); + + it("[[GetOwnProperty]] gives the value of an index property as a data property by default", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertEquals( + Object.getOwnPropertyDescriptor(proxy, 1), + { + configurable: true, + enumerable: true, + value: underlying[1], + writable: false, + }, + ); + assertEquals( + Object.getOwnPropertyDescriptor(proxy, 3), + { + configurable: true, + enumerable: true, + value: underlying[3], + writable: false, + }, + ); + /* test nonconfigurable data properties too */ + Object.freeze(underlying); + Object.defineProperty(proxy, "1", { configurable: false }); + assertEquals( + Object.getOwnPropertyDescriptor(proxy, 1), + { + configurable: false, + enumerable: true, + value: underlying[1], + writable: false, + }, + ); + }); + + it("[[GetOwnProperty]] gives a value of undefined if the underlying object does not have an index property as an own property", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertEquals( + Object.getOwnPropertyDescriptor(proxy, 0), + { + configurable: true, + enumerable: true, + value: undefined, + writable: false, + }, + ); + assertEquals( + Object.getOwnPropertyDescriptor(proxy, 2), + { + configurable: true, + enumerable: true, + value: undefined, + writable: false, + }, + ); + }); + + it("[[GetOwnProperty]] gives a value of undefined for index properties less than the length", () => { + const underlying = makeUnderlying(); + underlying.length = 0; + const proxy = denseProxy(underlying); + for (let i = 0; i < 4; ++i) { + assertStrictEquals( + Object.getOwnPropertyDescriptor(proxy, i), + undefined, + ); + } + }); + + it("[[GetOwnProperty]] gives a getter when the underlying object has a getter and an index property is not configurable", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + Object.freeze(underlying); + Object.defineProperty(proxy, "3", { configurable: false }); + const descriptor = Object.getOwnPropertyDescriptor(proxy, 3); + assertEquals( + descriptor, + { + configurable: false, + enumerable: true, + get: descriptor.get, + set: undefined, + }, + ); + }); + + describe("[[GetOwnProperty]].get.length", () => { + it("[[Get]] returns the correct length", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + Object.freeze(underlying); + Object.defineProperty(proxy, "3", { configurable: false }); + assertStrictEquals( + Object.getOwnPropertyDescriptor( + proxy, + "3", + ).get.length, + 0, + ); + }); + }); + + describe("[[GetOwnProperty]].get.name", () => { + it("[[Get]] returns the correct name", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + Object.freeze(underlying); + Object.defineProperty(proxy, "3", { configurable: false }); + assertStrictEquals( + Object.getOwnPropertyDescriptor( + proxy, + "3", + ).get.name, + "get 3", + ); + }); + }); + + it("[[HasProperty]] works when the property is not an index property or length", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + proxy.a = "etaoin"; + Object.getPrototypeOf(underlying).b = "shrdlu"; + assertStrictEquals( + Reflect.has(proxy, "a"), + true, + ); + assertStrictEquals( + Reflect.has(proxy, "b"), + true, + ); + assertStrictEquals( + Reflect.has(proxy, "z"), + false, + ); + }); + + it("[[HasProperty]] works for index properties less than the length", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + for (let i = 0; i < 4; ++i) { + assertStrictEquals( + Reflect.has(proxy, i), + true, + ); + } + delete underlying.length; + for (let i = 0; i < 4; ++i) { + assertStrictEquals( + Reflect.has(proxy, i), + Reflect.has(underlying, i), + ); + } + }); + + it("[[HasProperty]] works for index properties greater than or equal to the length", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.has(proxy, "4"), + false, + ); + assertStrictEquals( + Reflect.has(proxy, "5"), + false, + ); + underlying[4] = "inherited now"; + assertStrictEquals( + Reflect.has(proxy, "4"), + true, + ); + }); + }); + + describe("~length", () => { + it("[[DefineProperty]] allows changing nothing", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.defineProperty( + proxy, + "length", + { + configurable: true, + enumerable: false, + writable: false, + value: underlying.length >>> 0, + }, + ), + true, + ); + /* test nonconfigurable versions too */ + Object.freeze(underlying); + Object.defineProperty(proxy, "length", { configurable: false }); + assertStrictEquals( + Reflect.defineProperty( + proxy, + "length", + Object.getOwnPropertyDescriptor(proxy, "length"), + ), + true, + ); + }); + + it("[[DefineProperty]] does not allow a change in enumerablility", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.defineProperty(proxy, "length", { enumerable: true }), + false, + ); + }); + + it("[[DefineProperty]] does not allow a change in value", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.defineProperty(proxy, "length", { value: 0 }), + false, + ); + }); + + it("[[DefineProperty]] does not allow a change in getter", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.defineProperty(proxy, "length", { + get: () => underlying.length >>> 0, + }), + false, + ); + }); + + it("[[DefineProperty]] does not allow a change in setter", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.defineProperty(proxy, "length", { set: () => {} }), + false, + ); + }); + + it("[[DefineProperty]] does not allow a change in configurability if the length of the underlying object may change", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.defineProperty(proxy, "length", { + configurable: false, + }), + false, + ); + Object.defineProperty(underlying, "length", { + configurable: false, + get: () => 0, + }); + assertStrictEquals( + Reflect.defineProperty(proxy, "length", { + configurable: false, + }), + false, + ); + }); + + it("[[DefineProperty]] allows a change in configurability when it is safe to do so", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + Object.defineProperty(underlying, "length", { + configurable: false, + writable: false, + }); + assertStrictEquals( + Reflect.defineProperty(proxy, "length", { + configurable: false, + }), + true, + ); + }); + + it("[[Delete]] is forbidden", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.deleteProperty( + proxy, + "length", + ), + false, + ); + }); + + it("[[GetOwnProperty]] gives the value as a data property", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertEquals( + Object.getOwnPropertyDescriptor(proxy, "length"), + { + configurable: true, + enumerable: false, + value: underlying.length >>> 0, + writable: false, + }, + ); + /* test nonconfigurable data properties too */ + Object.freeze(underlying); + Object.defineProperty(proxy, "length", { configurable: false }); + assertEquals( + Object.getOwnPropertyDescriptor(proxy, "length"), + { + configurable: false, + enumerable: false, + value: underlying.length >>> 0, + writable: false, + }, + ); + }); + + it("[[GetOwnProperty]] gives 0 if the underlying object does not have the property as an own property", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + delete underlying.length; + Object.getPrototypeOf(underlying).length = 3; + assertEquals( + Object.getOwnPropertyDescriptor(proxy, "length"), + { + configurable: true, + enumerable: false, + value: 0, + writable: false, + }, + ); + }); + + it("[[HasProperty]] is always true", () => { + const underlying = makeUnderlying(); + const proxy = denseProxy(underlying); + assertStrictEquals( + Reflect.has(proxy, "length"), + true, + ); + delete underlying.length; + assertStrictEquals( + Reflect.has(proxy, "length"), + true, + ); + }); + }); +}); + +describe("fill", () => { + it("[[Call]] fills", () => { + assertEquals( + fill({ 1: "failure", length: 3 }, "success"), + { 0: "success", 1: "success", 2: "success", length: 3 }, + ); + }); + + it("[[Call]] can fill a range", () => { + assertEquals( + fill({ 1: "failure", length: 4 }, "success", 1, 3), + { 1: "success", 2: "success", length: 4 }, + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new fill([], null)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(fill.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(fill.name, "fill"); + }); + }); +}); + +describe("filter", () => { + it("[[Call]] filters", () => { + assertEquals( + filter(["failure", "success", ,], function ($) { + return !$ || $ == this; + }, "success"), + ["success"], + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new filter([], () => {})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(filter.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(filter.name, "filter"); + }); + }); +}); + +describe("findFirstIndex", () => { + it("[[Call]] returns undefined if no matching entry exists", () => { + assertStrictEquals(findFirstIndex([], () => true), undefined); + assertStrictEquals(findFirstIndex([1], () => false), undefined); + }); + + it("[[Call]] returns an index for the first match", () => { + assertStrictEquals( + findFirstIndex([, true, false], ($) => $ ?? true), + 1, + ); + assertStrictEquals( + findFirstIndex( + ["failure", "success", "success"], + ($) => $ == "success", + ), + 1, + ); + }); + + it("[[Call]] works on arraylike objects", () => { + assertStrictEquals( + findFirstIndex({ 1: "success", length: 2 }, ($) => $), + 1, + ); + assertStrictEquals( + findFirstIndex({ 1: "failure", length: 1 }, ($) => $), + undefined, + ); + }); + + it("[[Call]] only gets the value once", () => { + const get1 = spy(() => true); + findFirstIndex({ + get 1() { + return get1(); + }, + length: 2, + }, ($) => $); + assertSpyCalls(get1, 1); + }); + + it("[[Call]] passes the value, index, and this value to the callback", () => { + const arr = [, "failure", "success", "success"]; + const callback = spy(($) => $ === "success"); + const thisArg = {}; + findFirstIndex(arr, callback, thisArg); + assertSpyCalls(callback, 2); + assertSpyCall(callback, 0, { + args: ["failure", 1, arr], + self: thisArg, + }); + assertSpyCall(callback, 1, { + args: ["success", 2, arr], + self: thisArg, + }); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new findFirstIndex([], () => {})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(findFirstIndex.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(findFirstIndex.name, "findFirstIndex"); + }); + }); +}); -describe("findIndexedEntry", () => { +describe("findFirstIndexedEntry", () => { it("[[Call]] returns undefined if no matching entry exists", () => { - assertStrictEquals(findIndexedEntry([], () => true), void {}); - assertStrictEquals(findIndexedEntry([1], () => false), void {}); + assertStrictEquals( + findFirstIndexedEntry([], () => true), + undefined, + ); + assertStrictEquals( + findFirstIndexedEntry([1], () => false), + undefined, + ); }); it("[[Call]] returns an entry for the first match", () => { assertEquals( - findIndexedEntry([, true, false], ($) => $ ?? true), - [0, void {}], + findFirstIndexedEntry([, true, false], ($) => $ ?? true), + [1, true], ); assertEquals( - findIndexedEntry(["failure", "success"], ($) => $ == "success"), + findFirstIndexedEntry( + ["failure", "success", "success"], + ($) => $ == "success", + ), [1, "success"], ); }); it("[[Call]] works on arraylike objects", () => { assertEquals( - findIndexedEntry({ 1: "success", length: 2 }, ($) => $), + findFirstIndexedEntry({ 1: "success", length: 2 }, ($) => $), [1, "success"], ); assertEquals( - findIndexedEntry({ 1: "failure", length: 1 }, ($) => $), - void {}, + findFirstIndexedEntry({ 1: "failure", length: 1 }, ($) => $), + undefined, + ); + }); + + it("[[Call]] only gets the value once", () => { + const get1 = spy(() => true); + findFirstIndexedEntry({ + get 1() { + return get1(); + }, + length: 2, + }, ($) => $); + assertSpyCalls(get1, 1); + }); + + it("[[Call]] passes the value, index, and this value to the callback", () => { + const arr = [, "failure", "success", "success"]; + const callback = spy(($) => $ === "success"); + const thisArg = {}; + findFirstIndexedEntry(arr, callback, thisArg); + assertSpyCalls(callback, 2); + assertSpyCall(callback, 0, { + args: ["failure", 1, arr], + self: thisArg, + }); + assertSpyCall(callback, 1, { + args: ["success", 2, arr], + self: thisArg, + }); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new findFirstIndexedEntry([], () => {})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(findFirstIndexedEntry.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + findFirstIndexedEntry.name, + "findFirstIndexedEntry", + ); + }); + }); +}); + +describe("findFirstItem", () => { + it("[[Call]] returns undefined if no matching item exists", () => { + assertStrictEquals(findFirstItem([], () => true), undefined); + assertStrictEquals(findFirstItem([1], () => false), undefined); + }); + + it("[[Call]] returns the first match", () => { + assertStrictEquals( + findFirstItem([, true, false], ($) => $ ?? true), + true, + ); + assertStrictEquals( + findFirstItem( + ["failure", "success", "success"], + ($) => $ == "success", + ), + "success", + ); + }); + + it("[[Call]] works on arraylike objects", () => { + assertStrictEquals( + findFirstItem({ 1: "success", length: 2 }, ($) => $), + "success", + ); + assertStrictEquals( + findFirstItem({ 1: "failure", length: 1 }, ($) => $), + undefined, + ); + }); + + it("[[Call]] only gets the value once", () => { + const get1 = spy(() => true); + findFirstItem({ + get 1() { + return get1(); + }, + length: 2, + }, ($) => $); + assertSpyCalls(get1, 1); + }); + + it("[[Call]] passes the value, index, and this value to the callback", () => { + const arr = [, "failure", "success", "success"]; + const callback = spy(($) => $ === "success"); + const thisArg = {}; + findFirstItem(arr, callback, thisArg); + assertSpyCalls(callback, 2); + assertSpyCall(callback, 0, { + args: ["failure", 1, arr], + self: thisArg, + }); + assertSpyCall(callback, 1, { + args: ["success", 2, arr], + self: thisArg, + }); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new findFirstItem([], () => {})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(findFirstItem.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(findFirstItem.name, "findFirstItem"); + }); + }); +}); + +describe("findLastIndex", () => { + it("[[Call]] returns undefined if no matching entry exists", () => { + assertStrictEquals(findLastIndex([], () => true), undefined); + assertStrictEquals(findLastIndex([1], () => false), undefined); + }); + + it("[[Call]] returns an index for the first match", () => { + assertStrictEquals( + findLastIndex([true, false, ,], ($) => $ ?? true), + 0, + ); + assertStrictEquals( + findLastIndex( + ["success", "success", "failure"], + ($) => $ == "success", + ), + 1, + ); + }); + + it("[[Call]] works on arraylike objects", () => { + assertStrictEquals( + findLastIndex({ 1: "success", length: 2 }, ($) => $), + 1, + ); + assertStrictEquals( + findLastIndex({ 1: "failure", length: 1 }, ($) => $), + undefined, ); }); it("[[Call]] only gets the value once", () => { const get1 = spy(() => true); - findIndexedEntry({ + findLastIndex({ get 1() { return get1(); }, @@ -58,13 +1205,13 @@ describe("findIndexedEntry", () => { }); it("[[Call]] passes the value, index, and this value to the callback", () => { - const arr = ["failure", "success", "success"]; + const arr = ["success", "success", "failure", ,]; const callback = spy(($) => $ === "success"); const thisArg = {}; - findIndexedEntry(arr, callback, thisArg); + findLastIndex(arr, callback, thisArg); assertSpyCalls(callback, 2); assertSpyCall(callback, 0, { - args: ["failure", 0, arr], + args: ["failure", 2, arr], self: thisArg, }); assertSpyCall(callback, 1, { @@ -72,6 +1219,206 @@ describe("findIndexedEntry", () => { self: thisArg, }); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new findLastIndex([], () => {})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(findLastIndex.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(findLastIndex.name, "findLastIndex"); + }); + }); +}); + +describe("findLastIndexedEntry", () => { + it("[[Call]] returns undefined if no matching entry exists", () => { + assertStrictEquals( + findLastIndexedEntry([], () => true), + undefined, + ); + assertStrictEquals( + findLastIndexedEntry([1], () => false), + undefined, + ); + }); + + it("[[Call]] returns an index for the first match", () => { + assertEquals( + findLastIndexedEntry([true, false, ,], ($) => $ ?? true), + [0, true], + ); + assertEquals( + findLastIndexedEntry( + ["success", "success", "failure"], + ($) => $ == "success", + ), + [1, "success"], + ); + }); + + it("[[Call]] works on arraylike objects", () => { + assertEquals( + findLastIndexedEntry({ 1: "success", length: 2 }, ($) => $), + [1, "success"], + ); + assertEquals( + findLastIndexedEntry({ 1: "failure", length: 1 }, ($) => $), + undefined, + ); + }); + + it("[[Call]] only gets the value once", () => { + const get1 = spy(() => true); + findLastIndexedEntry({ + get 1() { + return get1(); + }, + length: 2, + }, ($) => $); + assertSpyCalls(get1, 1); + }); + + it("[[Call]] passes the value, index, and this value to the callback", () => { + const arr = ["success", "success", "failure", ,]; + const callback = spy(($) => $ === "success"); + const thisArg = {}; + findLastIndexedEntry(arr, callback, thisArg); + assertSpyCalls(callback, 2); + assertSpyCall(callback, 0, { + args: ["failure", 2, arr], + self: thisArg, + }); + assertSpyCall(callback, 1, { + args: ["success", 1, arr], + self: thisArg, + }); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new findLastIndexedEntry([], () => {})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(findLastIndexedEntry.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + findLastIndexedEntry.name, + "findLastIndexedEntry", + ); + }); + }); +}); + +describe("findLastItem", () => { + it("[[Call]] returns undefined if no matching entry exists", () => { + assertStrictEquals(findLastItem([], () => true), undefined); + assertStrictEquals(findLastItem([1], () => false), undefined); + }); + + it("[[Call]] returns an index for the first match", () => { + assertStrictEquals( + findLastItem([true, false, ,], ($) => $ ?? true), + true, + ); + assertStrictEquals( + findLastItem( + ["success", "success", "failure"], + ($) => $ == "success", + ), + "success", + ); + }); + + it("[[Call]] works on arraylike objects", () => { + assertStrictEquals( + findLastItem({ 1: "success", length: 2 }, ($) => $), + "success", + ); + assertStrictEquals( + findLastItem({ 1: "failure", length: 1 }, ($) => $), + undefined, + ); + }); + + it("[[Call]] only gets the value once", () => { + const get1 = spy(() => true); + findLastItem({ + get 1() { + return get1(); + }, + length: 2, + }, ($) => $); + assertSpyCalls(get1, 1); + }); + + it("[[Call]] passes the value, index, and this value to the callback", () => { + const arr = ["success", "success", "failure", ,]; + const callback = spy(($) => $ === "success"); + const thisArg = {}; + findLastItem(arr, callback, thisArg); + assertSpyCalls(callback, 2); + assertSpyCall(callback, 0, { + args: ["failure", 2, arr], + self: thisArg, + }); + assertSpyCall(callback, 1, { + args: ["success", 1, arr], + self: thisArg, + }); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new findLastItem([], () => {})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(findLastItem.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(findLastItem.name, "findLastItem"); + }); + }); +}); + +describe("flatmap", () => { + it("[[Call]] flatmaps", () => { + assertEquals( + flatmap([, "a", ["b"], [["c"]]], ($) => $), + ["a", "b", ["c"]], + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new flatmap([], () => {})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(flatmap.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(flatmap.name, "flatmap"); + }); + }); }); describe("isCollection", () => { @@ -140,3 +1487,55 @@ describe("isCollection", () => { ); }); }); + +describe("isDenseProxy", () => { + it("[[Call]] returns true for dense proxies", () => { + const underlying = []; + const proxy = denseProxy(underlying); + assertStrictEquals( + isDenseProxy(proxy), + true, + ); + }); + + it("[[Call]] returns false for others", () => { + assertStrictEquals( + isDenseProxy([]), + false, + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new isDenseProxy([])); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(isDenseProxy.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(isDenseProxy.name, "isDenseProxy"); + }); + }); +}); + +describe("toArray", () => { + it("[[Call]] returns an array of the values in a provided arraylike", () => { + assertEquals( + toArray({ 1: "success", length: 3 }), + [, "success", ,], + ); + }); +}); + +describe("toDenseArray", () => { + it("[[Call]] returns a dense array of the values in a provided arraylike", () => { + assertEquals( + toDenseArray({ 1: "success", length: 3 }), + [undefined, "success", undefined], + ); + }); +});