]> Lady’s Gitweb - Pisces/blobdiff - function.test.js
Add methods for own entries and values to object.js
[Pisces] / function.test.js
index 9711f792d4d4caf063f019be41388f3ab2908994..bf0e907a2df13688932447fbb084bc83940fdf82 100644 (file)
 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,
+  maybe,
   ordinaryHasInstance,
 } from "./function.js";
 
@@ -76,6 +83,12 @@ describe("bind", () => {
     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");
@@ -132,6 +145,12 @@ describe("call", () => {
     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");
@@ -165,6 +184,12 @@ describe("completesNormally", () => {
     assertThrows(() => new completesNormally(function () {}));
   });
 
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(completesNormally.length, 1);
+    });
+  });
+
   describe(".name", () => {
     it("[[Get]] returns the correct name", () => {
       assertStrictEquals(completesNormally.name, "completesNormally");
@@ -233,6 +258,12 @@ describe("construct", () => {
     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");
@@ -240,6 +271,98 @@ 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(
@@ -263,10 +386,65 @@ describe("createCallableFunction", () => {
     );
   });
 
+  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(
@@ -292,7 +470,17 @@ describe("createIllegalConstructor", () => {
       Object.getPrototypeOf(constructor.prototype),
       Object.prototype,
     );
-    assertEquals(constructor.prototype, {});
+    assertEquals(
+      constructor.prototype,
+      Object.create(Object.prototype, {
+        constructor: {
+          configurable: true,
+          enumerable: false,
+          value: constructor,
+          writable: true,
+        },
+      }),
+    );
     assert(
       !Object.getOwnPropertyDescriptor(constructor, "prototype")
         .writable,
@@ -303,6 +491,7 @@ describe("createIllegalConstructor", () => {
   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(
@@ -314,7 +503,7 @@ describe("createIllegalConstructor", () => {
       constructorPrototype,
     );
     assert(Object.hasOwn(constructor, "prototype"));
-    assertEquals(
+    assertStrictEquals(
       constructor.prototype,
       constructorPrototype.prototype,
     );
@@ -323,42 +512,59 @@ describe("createIllegalConstructor", () => {
         .writable,
     );
     assertStrictEquals(constructor.name, "etaoin");
+    assertStrictEquals(constructor.length, 3);
   });
 
-  it("[[Call]] allows the second argument to override the prototype of the constructor", () => {
-    const constructorPrototype = Object.create(null, {
-      name: { value: "etaoin" },
-      prototype: { value: {} },
-    });
-    const expectedPrototype = Object.create(null, {
-      name: { value: "shrdlu" },
-      prototype: { value: {} },
-    });
-    const constructor = createIllegalConstructor(
-      Object.create(constructorPrototype),
-      expectedPrototype,
-    );
-    assert(isConstructor(constructor));
+  it("[[Call]] allows the length to be overridden", () => {
     assertStrictEquals(
-      Object.getPrototypeOf(constructor),
-      expectedPrototype,
+      createIllegalConstructor(
+        function etaoin() {},
+        { length: 100 },
+      ).length,
+      100,
     );
-    assert(Object.hasOwn(constructor, "prototype"));
-    assertEquals(
-      constructor.prototype,
-      constructorPrototype.prototype,
+  });
+
+  it("[[Call]] allows the name to be overridden", () => {
+    assertStrictEquals(
+      createIllegalConstructor(
+        function etaoin() {},
+        { name: "shrdlu" },
+      ).name,
+      "shrdlu",
     );
-    assert(
-      !Object.getOwnPropertyDescriptor(constructor, "prototype")
-        .writable,
+  });
+
+  it("[[Call]] allows the prototype to be overridden", () => {
+    const obj = {};
+    assertStrictEquals(
+      createIllegalConstructor(
+        function etaoin() {},
+        { prototype: obj },
+      ).prototype,
+      obj,
     );
-    assertStrictEquals(constructor.name, "etaoin");
   });
 
   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(() => {
@@ -372,12 +578,332 @@ describe("createIllegalConstructor", () => {
       });
     });
   });
+});
+
+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(
-        createIllegalConstructor.name,
-        "createIllegalConstructor",
+        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,
+        },
       );
     });
   });
@@ -399,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");
@@ -465,6 +997,12 @@ describe("isCallable", () => {
     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");
@@ -531,6 +1069,12 @@ describe("isConstructor", () => {
     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");
@@ -538,6 +1082,42 @@ describe("isConstructor", () => {
   });
 });
 
+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]] 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(maybe.name, "maybe");
+    });
+  });
+});
+
 describe("ordinaryHasInstance", () => {
   it("[[Call]] walks the prototype chain", () => {
     const constructor = class {
@@ -558,6 +1138,12 @@ describe("ordinaryHasInstance", () => {
     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.046144 seconds and 4 git commands to generate.