]> Lady’s Gitweb - Pisces/blobdiff - collection.test.js
Add denseProxy, various collection.js improvements
[Pisces] / collection.test.js
index c0669325f1fc67a87cfd0288ec8a03f9d9b95719..079ef4b82545abc564665069d2d03a86b3e76faa 100644 (file)
-// ♓🌟 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 <https://mozilla.org/MPL/2.0/>.
+// SPDX-FileCopyrightText: 2022, 2023, 2025 Lady <https://www.ladys.computer/about/#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 <https://mozilla.org/MPL/2.0/>.
+ */
 
 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],
+    );
+  });
+});
This page took 0.407953 seconds and 4 git commands to generate.