// 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";
["etaoin", undefined, "shrdlu", undefined, "cmfwyp"],
);
});
+
+ 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");
+ });
+ });
});
describe("call", () => {
["etaoin", undefined, "shrdlu", undefined, "cmfwyp"],
);
});
+
+ 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");
+ });
+ });
+});
+
+describe("completesNormally", () => {
+ it("[[Call]] returns true for functions which complete normally", () => {
+ assertStrictEquals(completesNormally(() => {}), true);
+ });
+
+ it("[[Call]] returns false for functions which throw", () => {
+ assertStrictEquals(
+ completesNormally(() => {
+ throw null;
+ }),
+ false,
+ );
+ });
+
+ it("[[Call]] throws when the argument is not callable", () => {
+ assertThrows(() => completesNormally(null));
+ });
+
+ it("[[Call]] throws when the argument is not provided", () => {
+ assertThrows(() => completesNormally());
+ });
+
+ it("[[Construct]] throws an error", () => {
+ 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");
+ });
+ });
});
describe("construct", () => {
["etaoin", undefined, "shrdlu", undefined, "cmfwyp"],
);
});
+
+ 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");
+ });
+ });
+});
+
+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", () => {
const value = {};
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");
+ });
+ });
});
describe("isCallable", () => {
it("[[Call]] returns false for objects", () => {
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");
+ });
+ });
});
describe("isConstructor", () => {
it("[[Call]] returns false for objects", () => {
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");
+ });
+ });
});
-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(maybe.name, "maybe");
+ });
});
});
true,
);
});
+
+ 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(
+ ordinaryHasInstance.name,
+ "ordinaryHasInstance",
+ );
+ });
+ });
});