]> Lady’s Gitweb - Pisces/blobdiff - function.test.js
Add methods for own entries and values to object.js
[Pisces] / function.test.js
index e9286624b895a35c4a439c119f56760f558a6f00..bf0e907a2df13688932447fbb084bc83940fdf82 100644 (file)
@@ -8,21 +8,30 @@
 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
 
 import {
+  assert,
   assertEquals,
+  assertNotStrictEquals,
+  assertSpyCall,
+  assertSpyCalls,
   assertStrictEquals,
   assertThrows,
   describe,
   it,
+  spy,
 } from "./dev-deps.js";
 import {
   bind,
   call,
   completesNormally,
   construct,
+  createArrowFunction,
+  createCallableFunction,
+  createIllegalConstructor,
+  createProxyConstructor,
   identity,
   isCallable,
   isConstructor,
-  makeCallable,
+  maybe,
   ordinaryHasInstance,
 } from "./function.js";
 
@@ -70,6 +79,16 @@ describe("bind", () => {
     );
   });
 
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new bind(function () {}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(bind.length, 3);
+    });
+  });
+
   describe(".name", () => {
     it("[[Get]] returns the correct name", () => {
       assertStrictEquals(bind.name, "bind");
@@ -122,6 +141,16 @@ describe("call", () => {
     );
   });
 
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new call(function () {}, null, []));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(call.length, 3);
+    });
+  });
+
   describe(".name", () => {
     it("[[Get]] returns the correct name", () => {
       assertStrictEquals(call.name, "call");
@@ -144,14 +173,20 @@ describe("completesNormally", () => {
   });
 
   it("[[Call]] throws when the argument is not callable", () => {
-    assertThrows(() => {
-      completesNormally(null);
-    });
+    assertThrows(() => completesNormally(null));
   });
 
   it("[[Call]] throws when the argument is not provided", () => {
-    assertThrows(() => {
-      completesNormally();
+    assertThrows(() => completesNormally());
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new completesNormally(function () {}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(completesNormally.length, 1);
     });
   });
 
@@ -219,6 +254,16 @@ describe("construct", () => {
     );
   });
 
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new construct(function () {}, []));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(construct.length, 2);
+    });
+  });
+
   describe(".name", () => {
     it("[[Get]] returns the correct name", () => {
       assertStrictEquals(construct.name, "construct");
@@ -226,6 +271,644 @@ describe("construct", () => {
   });
 });
 
+describe("createArrowFunction", () => {
+  it("[[Call]] sets this to undefined", () => {
+    assertStrictEquals(
+      createArrowFunction(
+        function () {
+          return this;
+        },
+      ).call("fail"),
+      undefined,
+    );
+  });
+
+  it("[[Call]] transfers the provided arguments", () => {
+    assertEquals(
+      createArrowFunction(
+        function (...args) {
+          return [this, ...args];
+        },
+      ).call("failure", "etaoin", "shrdlu", "cmfwyp"),
+      [undefined, "etaoin", "shrdlu", "cmfwyp"],
+    );
+  });
+
+  it("[[Call]] correctly sets the length", () => {
+    assertStrictEquals(
+      createArrowFunction(
+        function (_a, _b, _c) {},
+      ).length,
+      3,
+    );
+  });
+
+  it("[[Call]] correctly sets the name", () => {
+    assertStrictEquals(
+      createArrowFunction(
+        function etaoin() {},
+      ).name,
+      "etaoin",
+    );
+  });
+
+  it("[[Call]] allows the length to be overridden", () => {
+    assertStrictEquals(
+      createArrowFunction(
+        function etaoin() {},
+        { length: 100 },
+      ).length,
+      100,
+    );
+  });
+
+  it("[[Call]] allows the name to be overridden", () => {
+    assertStrictEquals(
+      createArrowFunction(
+        function etaoin() {},
+        { name: "shrdlu" },
+      ).name,
+      "shrdlu",
+    );
+  });
+
+  it("[[Call]] returns an arrow function", () => {
+    assertThrows(() => new (createArrowFunction(function () {}))());
+  });
+
+  it("[[Call]] returns a function with no prototype property", () => {
+    assert(
+      !("prototype" in
+        createArrowFunction(function () {}, { prototype: {} })),
+    );
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new createArrowFunction(function () {}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(createArrowFunction.length, 1);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        createArrowFunction.name,
+        "createArrowFunction",
+      );
+    });
+  });
+});
+
+describe("createCallableFunction", () => {
+  it("[[Call]] transfers the first argument to this", () => {
+    assertStrictEquals(
+      createCallableFunction(
+        function () {
+          return this;
+        },
+      ).call("fail", "pass"),
+      "pass",
+    );
+  });
+
+  it("[[Call]] transfers the remaining arguments", () => {
+    assertEquals(
+      createCallableFunction(
+        function (...args) {
+          return [this, ...args];
+        },
+      ).call("failure", "etaoin", "shrdlu", "cmfwyp"),
+      ["etaoin", "shrdlu", "cmfwyp"],
+    );
+  });
+
+  it("[[Call]] correctly sets the length", () => {
+    assertStrictEquals(
+      createCallableFunction(
+        function (_a, _b, _c) {},
+      ).length,
+      4,
+    );
+  });
+
+  it("[[Call]] correctly sets the name", () => {
+    assertStrictEquals(
+      createCallableFunction(
+        function etaoin() {},
+      ).name,
+      "etaoin",
+    );
+  });
+
+  it("[[Call]] allows the length to be overridden", () => {
+    assertStrictEquals(
+      createCallableFunction(
+        function etaoin() {},
+        { length: 100 },
+      ).length,
+      100,
+    );
+  });
+
+  it("[[Call]] allows the name to be overridden", () => {
+    assertStrictEquals(
+      createCallableFunction(
+        function etaoin() {},
+        { name: "shrdlu" },
+      ).name,
+      "shrdlu",
+    );
+  });
+
+  it("[[Call]] returns an arrow function", () => {
+    assertThrows(() => new (createCallableFunction(function () {}))());
+  });
+
+  it("[[Call]] returns a function with no prototype property", () => {
+    assert(
+      !("prototype" in
+        createCallableFunction(function () {}, { prototype: {} })),
+    );
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new createCallableFunction(function () {}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(createCallableFunction.length, 1);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        createCallableFunction.name,
+        "createCallableFunction",
+      );
+    });
+  });
+});
+
+describe("createIllegalConstructor", () => {
+  it("[[Call]] returns a constructor", () => {
+    assert(isConstructor(createIllegalConstructor()));
+  });
+
+  it("[[Call]] works as expected when provided with no arguments", () => {
+    const constructor = createIllegalConstructor();
+    assertStrictEquals(
+      Object.getPrototypeOf(constructor),
+      Function.prototype,
+    );
+    assertStrictEquals(
+      Object.getPrototypeOf(constructor.prototype),
+      Object.prototype,
+    );
+    assertEquals(
+      constructor.prototype,
+      Object.create(Object.prototype, {
+        constructor: {
+          configurable: true,
+          enumerable: false,
+          value: constructor,
+          writable: true,
+        },
+      }),
+    );
+    assert(
+      !Object.getOwnPropertyDescriptor(constructor, "prototype")
+        .writable,
+    );
+    assertStrictEquals(constructor.name, "");
+  });
+
+  it("[[Call]] returns a correctly‐formed constructor when provided one argument", () => {
+    const constructorPrototype = Object.create(null, {
+      name: { value: "etaoin" },
+      length: { value: "3" },
+      prototype: { value: {} },
+    });
+    const constructor = createIllegalConstructor(
+      Object.create(constructorPrototype),
+    );
+    assert(isConstructor(constructor));
+    assertStrictEquals(
+      Object.getPrototypeOf(constructor),
+      constructorPrototype,
+    );
+    assert(Object.hasOwn(constructor, "prototype"));
+    assertStrictEquals(
+      constructor.prototype,
+      constructorPrototype.prototype,
+    );
+    assert(
+      !Object.getOwnPropertyDescriptor(constructor, "prototype")
+        .writable,
+    );
+    assertStrictEquals(constructor.name, "etaoin");
+    assertStrictEquals(constructor.length, 3);
+  });
+
+  it("[[Call]] allows the length to be overridden", () => {
+    assertStrictEquals(
+      createIllegalConstructor(
+        function etaoin() {},
+        { length: 100 },
+      ).length,
+      100,
+    );
+  });
+
+  it("[[Call]] allows the name to be overridden", () => {
+    assertStrictEquals(
+      createIllegalConstructor(
+        function etaoin() {},
+        { name: "shrdlu" },
+      ).name,
+      "shrdlu",
+    );
+  });
+
+  it("[[Call]] allows the prototype to be overridden", () => {
+    const obj = {};
+    assertStrictEquals(
+      createIllegalConstructor(
+        function etaoin() {},
+        { prototype: obj },
+      ).prototype,
+      obj,
+    );
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new createIllegalConstructor(function () {}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(createIllegalConstructor.length, 1);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        createIllegalConstructor.name,
+        "createIllegalConstructor",
+      );
+    });
+  });
+
+  describe("~", () => {
+    it("[[Call]] throws an error", () => {
+      assertThrows(() => {
+        createIllegalConstructor(function () {})();
+      });
+    });
+
+    it("[[Construct]] throws an error", () => {
+      assertThrows(() => {
+        createIllegalConstructor(function () {})();
+      });
+    });
+  });
+});
+
+describe("createProxyConstructor", () => {
+  it("[[Call]] returns a constructor", () => {
+    assert(isConstructor(createProxyConstructor({})));
+  });
+
+  it("[[Call]] throws with no arguments", () => {
+    assertThrows(() => createProxyConstructor());
+  });
+
+  it("[[Call]] throws if the second argument is not a constructor or undefined", () => {
+    assertThrows(() => createProxyConstructor({}, () => {}));
+  });
+
+  it("[[Call]] throws if the third argument is not a constructor or undefined", () => {
+    assertThrows(() =>
+      createProxyConstructor({}, undefined, () => {})
+    );
+  });
+
+  it("[[Call]] creates a proper proxy constructor", () => {
+    const constructorPrototype = function etaoin(_) {};
+    const constructor = class Constructor
+      extends constructorPrototype {
+      constructor(_1, _2, _3) {}
+    };
+    const proxyConstructor = createProxyConstructor(
+      {},
+      constructor,
+    );
+    assert(isConstructor(proxyConstructor));
+    assertStrictEquals(
+      Object.getPrototypeOf(proxyConstructor),
+      Proxy,
+    );
+    assertStrictEquals(proxyConstructor.prototype, undefined);
+    assertStrictEquals(proxyConstructor.name, "ConstructorProxy");
+    assertStrictEquals(proxyConstructor.length, 3);
+  });
+
+  it("[[Call]] allows the length to be overridden", () => {
+    assertStrictEquals(
+      createProxyConstructor({}, undefined, undefined, {
+        length: 100,
+      }).length,
+      100,
+    );
+  });
+
+  it("[[Call]] allows the name to be overridden", () => {
+    assertStrictEquals(
+      createProxyConstructor({}, function etaoin() {}, undefined, {
+        name: "shrdlu",
+      }).name,
+      "shrdlu",
+    );
+  });
+
+  it("[[Call]] does not allow the prototype to be overridden", () => {
+    assertStrictEquals(
+      createProxyConstructor({}, undefined, undefined, {
+        prototype: {},
+      }).prototype,
+      undefined,
+    );
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new createProxyConstructor({}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(createProxyConstructor.length, 2);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        createProxyConstructor.name,
+        "createProxyConstructor",
+      );
+    });
+  });
+
+  describe("~", () => {
+    it("[[Call]] throws an error", () => {
+      assertThrows(() => {
+        createProxyConstructor({})();
+      });
+    });
+
+    it("[[Construct]] produces a proxy", () => {
+      const obj = {};
+      const proxyConstructor = createProxyConstructor({
+        get(O, P, Receiver) {
+          if (P === "etaoin") {
+            return Reflect.get(O, P, Receiver) ?? "success";
+          } else {
+            return Reflect.get(O, P, Receiver);
+          }
+        },
+      }, function () {
+        return obj;
+      });
+      const proxy = new proxyConstructor();
+      assertStrictEquals(proxy.etaoin, "success");
+      obj.etaoin = "shrdlu";
+      assertStrictEquals(proxy.etaoin, "shrdlu");
+    });
+
+    it("[[Construct]] receives the expected new.target", () => {
+      const constructor = function Constructor() {
+        return { name: new.target.name };
+      };
+      assertStrictEquals(
+        new (createProxyConstructor({}, constructor))().name,
+        "Constructor",
+      );
+      assertStrictEquals(
+        new (createProxyConstructor(
+          {},
+          constructor,
+          function NewTarget() {},
+        ))().name,
+        "NewTarget",
+      );
+    });
+  });
+
+  describe("~is[[.name]]", () => {
+    it("[[GetOwnProperty]] defines the appropriate method", () => {
+      assertNotStrictEquals(
+        Object.getOwnPropertyDescriptor(
+          createProxyConstructor({}),
+          "isProxy",
+        ),
+        undefined,
+      );
+      assertNotStrictEquals(
+        Object.getOwnPropertyDescriptor(
+          createProxyConstructor({}, function Base() {}),
+          "isBaseProxy",
+        ),
+        undefined,
+      );
+      assertNotStrictEquals(
+        Object.getOwnPropertyDescriptor(
+          createProxyConstructor({}, function Bad() {}, undefined, {
+            name: "⸺",
+          }),
+          "is⸺",
+        ),
+        undefined,
+      );
+    });
+
+    it("[[GetOwnProperty]] has the correct descriptor", () => {
+      const proxyConstructor = createProxyConstructor({});
+      assertEquals(
+        Object.getOwnPropertyDescriptor(
+          proxyConstructor,
+          "isProxy",
+        ),
+        {
+          configurable: true,
+          enumerable: false,
+          value: proxyConstructor.isProxy,
+          writable: true,
+        },
+      );
+    });
+
+    it("[[Call]] returns true for created proxies", () => {
+      const proxyConstructor = createProxyConstructor({});
+      const proxy = new proxyConstructor();
+      assertStrictEquals(
+        proxyConstructor.isProxy(proxy),
+        true,
+      );
+    });
+
+    it("[[Call]] returns false for nonproxies", () => {
+      const constructor = function Base() {};
+      const proxyConstructor = createProxyConstructor({}, constructor);
+      assertStrictEquals(
+        proxyConstructor.isBaseProxy(new constructor()),
+        false,
+      );
+    });
+
+    it("[[Construct]] throws an error", () => {
+      const proxyConstructor = createProxyConstructor({});
+      assertThrows(() => new proxyConstructor.isProxy({}));
+    });
+
+    describe(".length", () => {
+      it("[[Get]] returns the correct length", () => {
+        const proxyConstructor = createProxyConstructor({});
+        assertStrictEquals(proxyConstructor.isProxy.length, 1);
+      });
+    });
+
+    describe(".name", () => {
+      it("[[Get]] returns the correct name", () => {
+        const proxyConstructor = createProxyConstructor({});
+        assertStrictEquals(proxyConstructor.isProxy.name, "isProxy");
+        const otherProxyConstructor = createProxyConstructor(
+          {},
+          function Base() {},
+        );
+        assertStrictEquals(
+          otherProxyConstructor.isBaseProxy.name,
+          "isBaseProxy",
+        );
+      });
+    });
+  });
+
+  describe("~length", () => {
+    it("[[GetOwnProperty]] has the correct descriptor", () => {
+      assertEquals(
+        Object.getOwnPropertyDescriptor(
+          createProxyConstructor({}),
+          "length",
+        ),
+        {
+          configurable: true,
+          enumerable: false,
+          value: 1,
+          writable: false,
+        },
+      );
+    });
+  });
+
+  describe("~name", () => {
+    it("[[GetOwnProperty]] has the correct descriptor", () => {
+      assertEquals(
+        Object.getOwnPropertyDescriptor(
+          createProxyConstructor({}),
+          "name",
+        ),
+        {
+          configurable: true,
+          enumerable: false,
+          value: "Proxy",
+          writable: false,
+        },
+      );
+    });
+  });
+
+  describe("~prototype", () => {
+    it("[[GetOwnProperty]] has the correct descriptor", () => {
+      assertEquals(
+        Object.getOwnPropertyDescriptor(
+          createProxyConstructor({}),
+          "prototype",
+        ),
+        {
+          configurable: false,
+          enumerable: false,
+          value: undefined,
+          writable: false,
+        },
+      );
+    });
+  });
+
+  describe("~revocable", () => {
+    it("[[Call]] produces a revocable proxy", () => {
+      const obj = {};
+      const proxyConstructor = createProxyConstructor({
+        get(O, P, Receiver) {
+          if (P === "etaoin") {
+            return Reflect.get(O, P, Receiver) ?? "success";
+          } else {
+            return Reflect.get(O, P, Receiver);
+          }
+        },
+      }, function () {
+        return obj;
+      });
+      const { proxy, revoke } = proxyConstructor.revocable();
+      assertStrictEquals(proxy.etaoin, "success");
+      obj.etaoin = "shrdlu";
+      assertStrictEquals(proxy.etaoin, "shrdlu");
+      revoke();
+      assertThrows(() => proxy.etaoin);
+    });
+
+    it("[[Call]] receives the expected new.target", () => {
+      const constructor = function Constructor() {
+        return { name: new.target.name };
+      };
+      assertStrictEquals(
+        createProxyConstructor({}, constructor).revocable().proxy.name,
+        "Constructor",
+      );
+      assertStrictEquals(
+        createProxyConstructor(
+          {},
+          constructor,
+          function NewTarget() {},
+        ).revocable().proxy.name,
+        "NewTarget",
+      );
+    });
+
+    it("[[Construct]] throws an error", () => {
+      assertThrows(() => new (createProxyConstructor({})).revocable());
+    });
+
+    it("[[GetOwnProperty]] has the correct descriptor", () => {
+      const proxyConstructor = createProxyConstructor({});
+      assertEquals(
+        Object.getOwnPropertyDescriptor(proxyConstructor, "revocable"),
+        {
+          configurable: true,
+          enumerable: false,
+          value: proxyConstructor.revocable,
+          writable: true,
+        },
+      );
+    });
+  });
+});
+
 describe("identity", () => {
   it("[[Call]] returns what it is given", () => {
     const value = {};
@@ -242,6 +925,12 @@ describe("identity", () => {
     assertStrictEquals(new class extends identity {}(value), value);
   });
 
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(identity.length, 1);
+    });
+  });
+
   describe(".name", () => {
     it("[[Get]] returns the correct name", () => {
       assertStrictEquals(identity.name, "identity");
@@ -304,6 +993,16 @@ describe("isCallable", () => {
     assertStrictEquals(isCallable({}), false);
   });
 
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new isCallable(function () {}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(isCallable.length, 1);
+    });
+  });
+
   describe(".name", () => {
     it("[[Get]] returns the correct name", () => {
       assertStrictEquals(isCallable.name, "isCallable");
@@ -366,6 +1065,16 @@ describe("isConstructor", () => {
     assertStrictEquals(isConstructor({}), false);
   });
 
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new isConstructor(function () {}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(isConstructor.length, 1);
+    });
+  });
+
   describe(".name", () => {
     it("[[Get]] returns the correct name", () => {
       assertStrictEquals(isConstructor.name, "isConstructor");
@@ -373,32 +1082,38 @@ describe("isConstructor", () => {
   });
 });
 
-describe("makeCallable", () => {
-  it("[[Call]] transfers the first argument to this", () => {
-    assertStrictEquals(
-      makeCallable(
-        function () {
-          return this;
-        },
-      ).call("fail", "pass"),
-      "pass",
-    );
+describe("maybe", () => {
+  it("[[Call]] calls if not nullish", () => {
+    const wrapper = spy(() => "success");
+    assertStrictEquals(maybe(0, wrapper), "success");
+    assertSpyCalls(wrapper, 1);
+    assertSpyCall(wrapper, 0, {
+      args: [0],
+      self: undefined,
+      returned: "success",
+    });
   });
 
-  it("[[Call]] transfers the remaining arguments", () => {
-    assertEquals(
-      makeCallable(
-        function (...args) {
-          return [this, ...args];
-        },
-      ).call("failure", "etaoin", "shrdlu", "cmfwyp"),
-      ["etaoin", "shrdlu", "cmfwyp"],
-    );
+  it("[[Call]] does not call if nullish", () => {
+    const wrapper = spy(() => "success");
+    assertStrictEquals(maybe(null, wrapper), null);
+    assertStrictEquals(maybe(undefined, wrapper), undefined);
+    assertSpyCalls(wrapper, 0);
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new maybe(true, ($) => $));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(maybe.length, 2);
+    });
   });
 
   describe(".name", () => {
     it("[[Get]] returns the correct name", () => {
-      assertStrictEquals(makeCallable.name, "makeCallable");
+      assertStrictEquals(maybe.name, "maybe");
     });
   });
 });
@@ -419,6 +1134,16 @@ describe("ordinaryHasInstance", () => {
     );
   });
 
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new ordinaryHasInstance(function () {}, {}));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(ordinaryHasInstance.length, 2);
+    });
+  });
+
   describe(".name", () => {
     it("[[Get]] returns the correct name", () => {
       assertStrictEquals(
This page took 0.03762 seconds and 4 git commands to generate.