]> Lady’s Gitweb - Pisces/commitdiff
Remove dependencies of function.js; add tests
authorLady <redacted>
Sun, 17 Jul 2022 17:52:45 +0000 (10:52 -0700)
committerLady <redacted>
Fri, 12 May 2023 03:56:47 +0000 (20:56 -0700)
- No longer depends on mutable runtime bindings or other modules

- Unit tests for all function exports

function.js
function.test.js [new file with mode: 0644]

index 0b9ebcc5510a4caaa767622b60427d5d536d4e2e..46cab860fb27e655a44fba66d211b253758637da 100644 (file)
@@ -7,22 +7,71 @@
 // 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 { defineOwnProperty, isObject } from "./object.js";
+export const {
+  /**
+   * Creates a bound function from the provided function using the
+   * provided this value and arguments list.
+   *
+   * ☡ As with call and construct, the arguments must be passed as an
+   * array.
+   */
+  bind,
 
-/**
- * Creates a bound function from the provided function using the
- * provided this value and arguments list.
- *
- * ☡ As with call and construct, the arguments must be passed as an
- * array.
- */
-export const bind = (() => {
-  const callBind = Function.prototype.call.bind(
-    Function.prototype.bind,
-  );
-  const bind = ($, boundThis, boundArgs) =>
-    callBind($, boundThis, ...boundArgs);
-  return bind;
+  /**
+   * Returns a new function which calls the provided function with its
+   * first argument as the `this` value and the remaining arguments
+   * passed through.
+   *
+   * ※ This is effectively an alias for Function.prototype.call.bind.
+   */
+  makeCallable,
+} = (() => {
+  // ☡ Because these functions are used to initialize module constants,
+  // they can’t depend on imports from elsewhere.
+  const {
+    bind: functionBind,
+    call: functionCall,
+  } = Function.prototype;
+  const callBind = Reflect.apply(functionBind, functionCall, [
+    functionBind,
+  ]);
+  const {
+    create: objectCreate,
+    defineProperty: defineOwnProperty,
+    getPrototypeOf: getPrototype,
+  } = Object;
+  const { iterator: iteratorSymbol } = Symbol;
+  const { [iteratorSymbol]: arrayIterator } = Array.prototype;
+  const {
+    next: arrayIteratorNext,
+  } = getPrototype([][iteratorSymbol]());
+  const argumentIterablePrototype = {
+    [iteratorSymbol]() {
+      return {
+        next: callBind(
+          arrayIteratorNext,
+          call(arrayIterator, this.args, []),
+        ),
+      };
+    },
+  };
+  return {
+    bind: ($, boundThis, boundArgs) =>
+      callBind(
+        $,
+        boundThis,
+        ...objectCreate(
+          argumentIterablePrototype,
+          { args: { value: boundArgs } },
+        ),
+      ),
+    makeCallable: ($) =>
+      defineOwnProperty(
+        callBind(functionCall, $),
+        "length",
+        { value: $.length + 1 },
+      ),
+  };
 })();
 
 /**
@@ -43,47 +92,40 @@ export const call = Reflect.apply;
  */
 export const construct = Reflect.construct;
 
+/**
+ * Returns the provided value.
+ *
+ * ※ This function can be called as a constructor. When used in an
+ * `extends` clause and called via `super`, it will set the value of
+ * `this` to the provided value, enabling it to be extended with
+ * private class features.
+ */
+export const identity = function ($) {
+  return $;
+};
+
+/** Returns whether the provided value is callable. */
+export const isCallable = ($) => typeof $ === "function";
+
 /** Returns whether the provided value is a constructor. */
 export const isConstructor = ($) => {
-  if (!isObject($)) {
-    // The provided value is not an object.
+  // The provided value is an object.
+  try {
+    construct(
+      function () {},
+      [],
+      $,
+    ); // will throw if $ is not a constructor
+    return true;
+  } catch {
     return false;
-  } else {
-    // The provided value is an object.
-    try {
-      construct(
-        function () {},
-        [],
-        $,
-      ); // will throw if $ is not a constructor
-      return true;
-    } catch {
-      return false;
-    }
   }
 };
 
-/**
- * Returns a new function which calls the provided function with its
- * first argument as the `this` value and the remaining arguments
- * passed through.
- *
- * ※ This is effectively an alias for Function.prototype.call.bind.
- */
-export const makeCallable = (() => {
-       const functionCall = Function.prototype.call;
-       const callable = ($) => defineOwnProperty(
-               bind(functionCall, $, []),
-               "length",
-               { value: $.length + 1 },
-       );
-       return callable;
-})();
-
 /**
  * Returns whether the provided object inherits from the prototype of
  * the provided function.
  */
 export const ordinaryHasInstance = makeCallable(
-       Function.prototype[Symbol.hasInstance],
+  Function.prototype[Symbol.hasInstance],
 );
diff --git a/function.test.js b/function.test.js
new file mode 100644 (file)
index 0000000..4cfa85f
--- /dev/null
@@ -0,0 +1,292 @@
+// ♓🌟 Piscēs ∷ function.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 {
+  assert,
+  assertEquals,
+  assertStrictEquals,
+  describe,
+  it,
+} from "./dev-deps.js";
+import {
+  bind,
+  call,
+  construct,
+  identity,
+  isCallable,
+  isConstructor,
+  makeCallable,
+  ordinaryHasInstance,
+} from "./function.js";
+
+describe("bind", () => {
+  it("[[Call]] binds this", () => {
+    assertStrictEquals(
+      bind(
+        function () {
+          return this;
+        },
+        "pass",
+        [],
+      ).call("fail"),
+      "pass",
+    );
+  });
+
+  it("[[Call]] binds arguments", () => {
+    assertEquals(
+      bind(
+        function (...args) {
+          return [this, ...args];
+        },
+        "etaoin",
+        ["shrdlu"],
+      ).call("failure", "cmfwyp"),
+      ["etaoin", "shrdlu", "cmfwyp"],
+    );
+  });
+
+  it("[[Call]] works with any arraylike third argument", () => {
+    assertEquals(
+      bind(
+        function (...args) {
+          return [this, ...args];
+        },
+        "etaoin",
+        {
+          1: "shrdlu",
+          3: "failure",
+          length: 3,
+        },
+      ).call("failure", "cmfwyp"),
+      ["etaoin", undefined, "shrdlu", undefined, "cmfwyp"],
+    );
+  });
+});
+
+describe("call", () => {
+  it("[[Call]] calls with the provided this value", () => {
+    assertStrictEquals(
+      call(
+        function () {
+          return this;
+        },
+        "pass",
+        [],
+      ),
+      "pass",
+    );
+  });
+
+  it("[[Call]] calls with the provided arguments", () => {
+    assertEquals(
+      call(
+        function (...args) {
+          return [this, ...args];
+        },
+        "etaoin",
+        ["shrdlu", "cmfwyp"],
+      ),
+      ["etaoin", "shrdlu", "cmfwyp"],
+    );
+  });
+
+  it("[[Call]] works with any arraylike third argument", () => {
+    assertEquals(
+      call(
+        function (...args) {
+          return [this, ...args];
+        },
+        "etaoin",
+        {
+          1: "shrdlu",
+          3: "cmfwyp",
+          4: "failure",
+          length: 4,
+        },
+      ),
+      ["etaoin", undefined, "shrdlu", undefined, "cmfwyp"],
+    );
+  });
+});
+
+describe("construct", () => {
+  it("[[Call]] defaults to the constructor as the target", () => {
+    const constructor = class {};
+    assertStrictEquals(
+      Object.getPrototypeOf(construct(
+        constructor,
+        [],
+      )),
+      constructor.prototype,
+    );
+  });
+
+  it("[[Call]] constructs with the provided new target", () => {
+    const target = function () {};
+    assertStrictEquals(
+      construct(
+        function () {
+          return new.target;
+        },
+        [],
+        target,
+      ),
+      target,
+    );
+  });
+
+  it("[[Call]] constructs with the provided arguments", () => {
+    assertEquals(
+      construct(
+        function (...args) {
+          return [new.target.value, ...args];
+        },
+        ["shrdlu", "cmfwyp"],
+        Object.assign(function () {}, { value: "etaoin" }),
+      ),
+      ["etaoin", "shrdlu", "cmfwyp"],
+    );
+  });
+
+  it("[[Call]] works with any arraylike third argument", () => {
+    assertEquals(
+      construct(
+        function (...args) {
+          return [new.target.value, ...args];
+        },
+        {
+          1: "shrdlu",
+          3: "cmfwyp",
+          4: "failure",
+          length: 4,
+        },
+        Object.assign(function () {}, { value: "etaoin" }),
+      ),
+      ["etaoin", undefined, "shrdlu", undefined, "cmfwyp"],
+    );
+  });
+});
+
+describe("identity", () => {
+  it("[[Call]] returns what it is given", () => {
+    const value = {};
+    assertStrictEquals(identity(value), value);
+  });
+
+  it("[[Construct]] is constructable", () => {
+    const value = {};
+    assertStrictEquals(new identity(value), value);
+  });
+
+  it("[[Construct]] is subclassable", () => {
+    const value = {};
+    assertStrictEquals(new class extends identity {}(value), value);
+  });
+});
+
+describe("isCallable", () => {
+  it("[[Call]] returns true for ordinary functions", () => {
+    assert(isCallable(function () {}));
+  });
+
+  it("[[Call]] returns true for arrow functions", () => {
+    assert(isCallable(() => {}));
+  });
+
+  it("[[Call]] returns true for generator functions", () => {
+    assert(isCallable(function* () {}));
+  });
+
+  it("[[Call]] returns true for classes", () => {
+    assert(isCallable(class {}));
+  });
+
+  it("[[Call]] returns true for builtin functions", () => {
+    assert(isCallable(Math.ceil));
+  });
+
+  it("[[Call]] returns false for null", () => {
+    assert(!isCallable(null));
+  });
+
+  it("[[Call]] returns false for objects", () => {
+    assert(!isCallable({}));
+  });
+});
+
+describe("isConstructor", () => {
+  it("[[Call]] returns true for ordinary functions", () => {
+    assert(isConstructor(function () {}));
+  });
+
+  it("[[Call]] returns false for arrow functions", () => {
+    assert(!isConstructor(() => {}));
+  });
+
+  it("[[Call]] returns false for generator functions", () => {
+    assert(!isConstructor(function* () {}));
+  });
+
+  it("[[Call]] returns true for classes", () => {
+    assert(isConstructor(class {}));
+  });
+
+  it("[[Call]] returns false for builtin functions", () => {
+    assert(!isConstructor(Math.ceil));
+  });
+
+  it("[[Call]] returns false for null", () => {
+    assert(!isConstructor(null));
+  });
+
+  it("[[Call]] returns false for objects", () => {
+    assert(!isConstructor({}));
+  });
+});
+
+describe("makeCallable", () => {
+  it("[[Call]] transfers the first argument to this", () => {
+    assertStrictEquals(
+      makeCallable(
+        function () {
+          return this;
+        },
+      ).call("fail", "pass"),
+      "pass",
+    );
+  });
+
+  it("[[Call]] transfers the remaining arguments", () => {
+    assertEquals(
+      makeCallable(
+        function (...args) {
+          return [this, ...args];
+        },
+      ).call("failure", "etaoin", "shrdlu", "cmfwyp"),
+      ["etaoin", "shrdlu", "cmfwyp"],
+    );
+  });
+});
+
+describe("ordinaryHasInstance", () => {
+  it("[[Call]] walks the prototype chain", () => {
+    const constructor = class {
+      [Symbol.hasInstance]() {
+        return false;
+      }
+    };
+    assert(
+      ordinaryHasInstance(
+        constructor,
+        new class extends constructor {}(),
+      ),
+    );
+  });
+});
This page took 0.066175 seconds and 4 git commands to generate.