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,
- toFunctionName,
} from "./function.js";
describe("bind", () => {
});
});
+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(
);
});
+ 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() {},
- "shrdlu",
+ { 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 () {}));
});
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,
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(
constructorPrototype,
);
assert(Object.hasOwn(constructor, "prototype"));
- assertEquals(
+ assertStrictEquals(
constructor.prototype,
constructorPrototype.prototype,
);
.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(() => {
});
});
});
+});
+
+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(createIllegalConstructor.length, 1);
+ 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,
+ },
);
});
});
});
});
-describe("ordinaryHasInstance", () => {
- it("[[Call]] walks the prototype chain", () => {
- const constructor = class {
- [Symbol.hasInstance]() {
- return false;
- }
- };
- assertStrictEquals(
- ordinaryHasInstance(
- constructor,
- new class extends constructor {}(),
- ),
- true,
- );
+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 ordinaryHasInstance(function () {}, {}));
+ assertThrows(() => new maybe(true, ($) => $));
});
describe(".length", () => {
it("[[Get]] returns the correct length", () => {
- assertStrictEquals(ordinaryHasInstance.length, 2);
+ assertStrictEquals(maybe.length, 2);
});
});
describe(".name", () => {
it("[[Get]] returns the correct name", () => {
- assertStrictEquals(
- ordinaryHasInstance.name,
- "ordinaryHasInstance",
- );
+ assertStrictEquals(maybe.name, "maybe");
});
});
});
-describe("toFunctionName", () => {
- it("[[Call]] works with strings and no prefix", () => {
- assertStrictEquals(toFunctionName("etaoin"), "etaoin");
- });
-
- it("[[Call]] works with objects and no prefix", () => {
- assertStrictEquals(
- toFunctionName({
- toString() {
- return "etaoin";
- },
- }),
- "etaoin",
- );
- });
-
- it("[[Call]] works with descriptionless symbols and no prefix", () => {
- assertStrictEquals(toFunctionName(Symbol()), "");
- });
-
- it("[[Call]] works with empty description symbols and no prefix", () => {
- assertStrictEquals(toFunctionName(Symbol("")), "[]");
- });
-
- it("[[Call]] works with described symbols and no prefix", () => {
- assertStrictEquals(toFunctionName(Symbol("etaoin")), "[etaoin]");
- });
-
- it("[[Call]] works with strings and a prefix", () => {
- assertStrictEquals(toFunctionName("etaoin", "foo"), "foo etaoin");
- });
-
- it("[[Call]] works with objects and no prefix", () => {
- assertStrictEquals(
- toFunctionName({
- toString() {
- return "etaoin";
- },
- }, "foo"),
- "foo etaoin",
- );
- });
-
- it("[[Call]] works with descriptionless symbols and no prefix", () => {
- assertStrictEquals(toFunctionName(Symbol(), "foo"), "foo ");
- });
-
- it("[[Call]] works with empty description symbols and no prefix", () => {
- assertStrictEquals(toFunctionName(Symbol(""), "foo"), "foo []");
- });
-
- it("[[Call]] works with described symbols and no prefix", () => {
+describe("ordinaryHasInstance", () => {
+ it("[[Call]] walks the prototype chain", () => {
+ const constructor = class {
+ [Symbol.hasInstance]() {
+ return false;
+ }
+ };
assertStrictEquals(
- toFunctionName(Symbol("etaoin"), "foo"),
- "foo [etaoin]",
+ ordinaryHasInstance(
+ constructor,
+ new class extends constructor {}(),
+ ),
+ true,
);
});
it("[[Construct]] throws an error", () => {
- assertThrows(() => new toFunctionName(""));
+ assertThrows(() => new ordinaryHasInstance(function () {}, {}));
});
describe(".length", () => {
it("[[Get]] returns the correct length", () => {
- assertStrictEquals(toFunctionName.length, 1);
+ assertStrictEquals(ordinaryHasInstance.length, 2);
});
});
describe(".name", () => {
it("[[Get]] returns the correct name", () => {
assertStrictEquals(
- toFunctionName.name,
- "toFunctionName",
+ ordinaryHasInstance.name,
+ "ordinaryHasInstance",
);
});
});