+// ♓🌟 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/>.
+
+import {
+ assertEquals,
+ assertSpyCall,
+ assertSpyCalls,
+ assertStrictEquals,
+ assertThrows,
+ describe,
+ it,
+ spy,
+} from "./dev-deps.js";
+import {
+ canonicalNumericIndexString,
+ findIndexedEntry,
+ isArrayIndexString,
+ isCollection,
+ isConcatSpreadable,
+ isIntegerIndexString,
+ lengthOfArrayLike,
+ toIndex,
+ toLength,
+} from "./collection.js";
+
+describe("canonicalNumericIndexString", () => {
+ it("[[Call]] returns undefined for nonstrings", () => {
+ assertStrictEquals(canonicalNumericIndexString(1), void {});
+ });
+
+ it("[[Call]] returns undefined for noncanonical strings", () => {
+ assertStrictEquals(canonicalNumericIndexString(""), void {});
+ assertStrictEquals(canonicalNumericIndexString("01"), void {});
+ assertStrictEquals(
+ canonicalNumericIndexString("9007199254740993"),
+ void {},
+ );
+ });
+
+ it('[[Call]] returns -0 for "-0"', () => {
+ assertStrictEquals(canonicalNumericIndexString("-0"), -0);
+ });
+
+ it("[[Call]] returns the corresponding number for canonical strings", () => {
+ assertStrictEquals(canonicalNumericIndexString("0"), 0);
+ assertStrictEquals(canonicalNumericIndexString("-0.25"), -0.25);
+ assertStrictEquals(
+ canonicalNumericIndexString("9007199254740992"),
+ 9007199254740992,
+ );
+ assertStrictEquals(canonicalNumericIndexString("NaN"), 0 / 0);
+ assertStrictEquals(canonicalNumericIndexString("Infinity"), 1 / 0);
+ assertStrictEquals(
+ canonicalNumericIndexString("-Infinity"),
+ -1 / 0,
+ );
+ });
+});
+
+describe("findIndexedEntry", () => {
+ it("[[Call]] returns undefined if no matching entry exists", () => {
+ assertStrictEquals(findIndexedEntry([], () => true), void {});
+ assertStrictEquals(findIndexedEntry([1], () => false), void {});
+ });
+
+ it("[[Call]] returns an entry for the first match", () => {
+ assertEquals(
+ findIndexedEntry([, true, false], ($) => $ ?? true),
+ [0, void {}],
+ );
+ assertEquals(
+ findIndexedEntry(["failure", "success"], ($) => $ == "success"),
+ [1, "success"],
+ );
+ });
+
+ it("[[Call]] works on arraylike objects", () => {
+ assertEquals(
+ findIndexedEntry({ 1: "success", length: 2 }, ($) => $),
+ [1, "success"],
+ );
+ assertEquals(
+ findIndexedEntry({ 1: "failure", length: 1 }, ($) => $),
+ void {},
+ );
+ });
+
+ it("[[Call]] only gets the value once", () => {
+ const get1 = spy(() => true);
+ findIndexedEntry({
+ 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 = {};
+ findIndexedEntry(arr, callback, thisArg);
+ assertSpyCalls(callback, 2);
+ assertSpyCall(callback, 0, {
+ args: ["failure", 0, arr],
+ self: thisArg,
+ });
+ assertSpyCall(callback, 1, {
+ args: ["success", 1, arr],
+ self: thisArg,
+ });
+ });
+});
+
+describe("isArrayIndexString", () => {
+ it("[[Call]] returns false for nonstrings", () => {
+ assertStrictEquals(isArrayIndexString(1), false);
+ });
+
+ it("[[Call]] returns false for noncanonical strings", () => {
+ assertStrictEquals(isArrayIndexString(""), false);
+ assertStrictEquals(isArrayIndexString("01"), false);
+ assertStrictEquals(isArrayIndexString("9007199254740993"), false);
+ });
+
+ it("[[Call]] returns false for nonfinite numbers", () => {
+ assertStrictEquals(isArrayIndexString("NaN"), false);
+ assertStrictEquals(isArrayIndexString("Infinity"), false);
+ assertStrictEquals(isArrayIndexString("-Infinity"), false);
+ });
+
+ it("[[Call]] returns false for negative numbers", () => {
+ assertStrictEquals(isArrayIndexString("-0"), false);
+ assertStrictEquals(isArrayIndexString("-1"), false);
+ });
+
+ it("[[Call]] returns false for nonintegers", () => {
+ assertStrictEquals(isArrayIndexString("0.25"), false);
+ assertStrictEquals(isArrayIndexString("1.1"), false);
+ });
+
+ it("[[Call]] returns false for numbers greater than or equal to -1 >>> 0", () => {
+ assertStrictEquals(isArrayIndexString(String(-1 >>> 0)), false);
+ assertStrictEquals(
+ isArrayIndexString(String((-1 >>> 0) + 1)),
+ false,
+ );
+ });
+
+ it("[[Call]] returns true for array lengths less than -1 >>> 0", () => {
+ assertStrictEquals(isArrayIndexString("0"), true);
+ assertStrictEquals(
+ isArrayIndexString(String((-1 >>> 0) - 1)),
+ true,
+ );
+ });
+});
+
+describe("isCollection", () => {
+ it("[[Call]] returns false for primitives", () => {
+ assertStrictEquals(isCollection("failure"), false);
+ });
+
+ it("[[Call]] returns false if length throws", () => {
+ assertStrictEquals(
+ isCollection({
+ get length() {
+ throw void {};
+ },
+ }),
+ false,
+ );
+ });
+
+ it("[[Call]] returns false if length is not an integer index and cannot be converted to one", () => {
+ assertStrictEquals(
+ isCollection({ length: -1, [Symbol.isConcatSpreadable]: true }),
+ false,
+ );
+ assertStrictEquals(
+ isCollection({
+ length: Infinity,
+ [Symbol.isConcatSpreadable]: true,
+ }),
+ false,
+ );
+ assertStrictEquals(
+ isCollection({
+ length: 9007199254740992,
+ [Symbol.isConcatSpreadable]: true,
+ }),
+ false,
+ );
+ });
+
+ it("[[Call]] returns true if length is an integer index and the object is concat spreadable", () => {
+ assertStrictEquals(
+ isCollection({ length: 1, [Symbol.isConcatSpreadable]: true }),
+ true,
+ );
+ assertStrictEquals(
+ isCollection({ length: 0, [Symbol.isConcatSpreadable]: true }),
+ true,
+ );
+ assertStrictEquals(
+ isCollection({
+ length: 9007199254740991,
+ [Symbol.isConcatSpreadable]: true,
+ }),
+ true,
+ );
+ });
+
+ it("[[Call]] returns true if length can be converted to an index without throwing an error and the object is concat spreadable", () => {
+ assertStrictEquals(
+ isCollection({ length: -0, [Symbol.isConcatSpreadable]: true }),
+ true,
+ );
+ assertStrictEquals(
+ isCollection({ length: NaN, [Symbol.isConcatSpreadable]: true }),
+ true,
+ );
+ });
+});
+
+describe("isConcatSpreadable", () => {
+ it("[[Call]] returns false for primitives", () => {
+ assertStrictEquals(isConcatSpreadable("failure"), false);
+ });
+
+ it("[[Call]] returns false if [Symbol.isConcatSpreadable] is null or false", () => {
+ assertStrictEquals(
+ isConcatSpreadable(
+ Object.assign([], { [Symbol.isConcatSpreadable]: null }),
+ ),
+ false,
+ );
+ assertStrictEquals(
+ isConcatSpreadable(
+ Object.assign([], { [Symbol.isConcatSpreadable]: false }),
+ ),
+ false,
+ );
+ });
+
+ it("[[Call]] returns true if [Symbol.isConcatSpreadable] is undefined and the object is an array", () => {
+ assertStrictEquals(
+ isConcatSpreadable(
+ Object.assign([], { [Symbol.isConcatSpreadable]: undefined }),
+ ),
+ true,
+ );
+ });
+
+ it("[[Call]] returns true if [Symbol.isConcatSpreadable] is true", () => {
+ assertStrictEquals(
+ isConcatSpreadable({ [Symbol.isConcatSpreadable]: true }),
+ true,
+ );
+ });
+});
+
+describe("isIntegerIndexString", () => {
+ it("[[Call]] returns false for nonstrings", () => {
+ assertStrictEquals(isIntegerIndexString(1), false);
+ });
+
+ it("[[Call]] returns false for noncanonical strings", () => {
+ assertStrictEquals(isIntegerIndexString(""), false);
+ assertStrictEquals(isIntegerIndexString("01"), false);
+ assertStrictEquals(
+ isIntegerIndexString("9007199254740993"),
+ false,
+ );
+ });
+
+ it("[[Call]] returns false for nonfinite numbers", () => {
+ assertStrictEquals(isIntegerIndexString("NaN"), false);
+ assertStrictEquals(isIntegerIndexString("Infinity"), false);
+ assertStrictEquals(isIntegerIndexString("-Infinity"), false);
+ });
+
+ it("[[Call]] returns false for negative numbers", () => {
+ assertStrictEquals(isIntegerIndexString("-0"), false);
+ assertStrictEquals(isIntegerIndexString("-1"), false);
+ });
+
+ it("[[Call]] returns false for nonintegers", () => {
+ assertStrictEquals(isIntegerIndexString("0.25"), false);
+ assertStrictEquals(isIntegerIndexString("1.1"), false);
+ });
+
+ it("[[Call]] returns false for numbers greater than or equal to 2 ** 53", () => {
+ assertStrictEquals(
+ isIntegerIndexString("9007199254740992"),
+ false,
+ );
+ });
+
+ it("[[Call]] returns true for safe canonical integer strings", () => {
+ assertStrictEquals(isIntegerIndexString("0"), true);
+ assertStrictEquals(isIntegerIndexString("9007199254740991"), true);
+ });
+});
+
+describe("lengthOfArrayLike", () => {
+ it("[[Call]] returns the length", () => {
+ assertStrictEquals(
+ lengthOfArrayLike({ length: 9007199254740991 }),
+ 9007199254740991,
+ );
+ });
+
+ it("[[Call]] returns a non·nan result", () => {
+ assertStrictEquals(lengthOfArrayLike({ length: NaN }), 0);
+ assertStrictEquals(lengthOfArrayLike({ length: "failure" }), 0);
+ });
+
+ it("[[Call]] returns an integral result", () => {
+ assertStrictEquals(lengthOfArrayLike({ length: 0.25 }), 0);
+ assertStrictEquals(lengthOfArrayLike({ length: 1.1 }), 1);
+ });
+
+ it("[[Call]] returns a result greater than or equal to zero", () => {
+ assertStrictEquals(lengthOfArrayLike({ length: -0 }), 0);
+ assertStrictEquals(lengthOfArrayLike({ length: -1 }), 0);
+ assertStrictEquals(lengthOfArrayLike({ length: -Infinity }), 0);
+ });
+
+ it("[[Call]] returns a result less than 2 ** 53", () => {
+ assertStrictEquals(
+ lengthOfArrayLike({ length: 9007199254740992 }),
+ 9007199254740991,
+ );
+ assertStrictEquals(
+ lengthOfArrayLike({ length: Infinity }),
+ 9007199254740991,
+ );
+ });
+});
+
+describe("toIndex", () => {
+ it("[[Call]] returns an index", () => {
+ assertStrictEquals(toIndex(9007199254740991), 9007199254740991);
+ });
+
+ it("[[Call]] returns zero for a zerolike result", () => {
+ assertStrictEquals(toIndex(NaN), 0);
+ assertStrictEquals(toIndex("failure"), 0);
+ assertStrictEquals(toIndex(-0), 0);
+ });
+
+ it("[[Call]] rounds down to the nearest integer", () => {
+ assertStrictEquals(toIndex(0.25), 0);
+ assertStrictEquals(toIndex(1.1), 1);
+ });
+
+ it("[[Call]] throws when provided a negative number", () => {
+ assertThrows(() => toIndex(-1));
+ assertThrows(() => toIndex(-Infinity));
+ });
+
+ it("[[Call]] throws when provided a number greater than or equal to 2 ** 53", () => {
+ assertThrows(() => toIndex(9007199254740992));
+ assertThrows(() => toIndex(Infinity));
+ });
+});
+
+describe("toLength", () => {
+ it("[[Call]] returns a length", () => {
+ assertStrictEquals(toLength(9007199254740991), 9007199254740991);
+ });
+
+ it("[[Call]] returns a non·nan result", () => {
+ assertStrictEquals(toLength(NaN), 0);
+ assertStrictEquals(toLength("failure"), 0);
+ });
+
+ it("[[Call]] returns an integral result", () => {
+ assertStrictEquals(toLength(0.25), 0);
+ assertStrictEquals(toLength(1.1), 1);
+ });
+
+ it("[[Call]] returns a result greater than or equal to zero", () => {
+ assertStrictEquals(toLength(-0), 0);
+ assertStrictEquals(toLength(-1), 0);
+ assertStrictEquals(toLength(-Infinity), 0);
+ });
+
+ it("[[Call]] returns a result less than 2 ** 53", () => {
+ assertStrictEquals(toLength(9007199254740992), 9007199254740991);
+ assertStrictEquals(toLength(Infinity), 9007199254740991);
+ });
+});