blob: 31b25cab9f2d88d8df59a0b4ecb35eef3765e380 [file] [log] [blame]
export function isConstructor(o) {
assert_equals(typeof o, "function", "Must be a function according to typeof");
assert_true(isConstructorTest(o), "Must be a constructor according to the meta-object protocol");
assert_throws(new TypeError(), () => o(), "Attempting to call (not construct) must throw");
}
export function functionLength(o, expected, label) {
const lengthExpected = { writable: false, enumerable: false, configurable: true };
const { value } = propertyDescriptor(o, "length", lengthExpected);
assert_equals(value, expected, `${formatLabel(label)}length value`);
}
export function functionName(o, expected, label) {
const lengthExpected = { writable: false, enumerable: false, configurable: true };
const { value } = propertyDescriptor(o, "name", lengthExpected);
assert_equals(value, expected, `${formatLabel(label)}name value`);
}
export function hasClassPrototype(o) {
const prototypeExpected = { writable: false, enumerable: false, configurable: false };
const { value } = propertyDescriptor(o, "prototype", prototypeExpected);
assert_equals(typeof value, "object", "prototype must be an object");
assert_not_equals(value, null, "prototype must not be null");
}
export function hasPrototypeConstructorLink(klass) {
const constructorExpected = { writable: true, enumerable: false, configurable: true };
const { value } = propertyDescriptor(klass.prototype, "constructor", constructorExpected);
assert_equals(value, klass, "constructor property must match");
}
export function propertyKeys(o, expectedNames, expectedSymbols, label) {
label = formatLabel(label);
assert_array_equals(Object.getOwnPropertyNames(o), expectedNames, `${label}property names`);
assert_array_equals(Object.getOwnPropertySymbols(o), expectedSymbols,
`${label}property symbols`);
}
export function methods(o, expectedMethods) {
for (const [name, length] of Object.entries(expectedMethods)) {
method(o, name, length);
}
}
export function accessors(o, expectedAccessors) {
for (const [name, accessorTypes] of Object.entries(expectedAccessors)) {
accessor(o, name, accessorTypes);
}
}
function method(o, prop, length) {
const methodExpected = { writable: true, enumerable: false, configurable: true };
const { value } = propertyDescriptor(o, prop, methodExpected);
assert_equals(typeof value, "function", `${prop} method must be a function according to typeof`);
assert_false(isConstructorTest(value),
`${prop} method must not be a constructor according to the meta-object protocol`);
functionLength(value, length, prop);
functionName(value, prop, prop);
propertyKeys(value, ["length", "name"], [], prop);
}
function accessor(o, prop, expectedAccessorTypes) {
const accessorExpected = { enumerable: false, configurable: true };
const propDesc = propertyDescriptor(o, prop, accessorExpected);
for (const possibleType of ["get", "set"]) {
const accessorFunc = propDesc[possibleType];
if (expectedAccessorTypes.includes(possibleType)) {
const label = `${prop}'s ${possibleType}ter`;
assert_equals(typeof accessorFunc, "function",
`${label} must be a function according to typeof`);
assert_false(isConstructorTest(accessorFunc),
`${label} must not be a constructor according to the meta-object protocol`);
functionLength(accessorFunc, possibleType === "get" ? 0 : 1, label);
functionName(accessorFunc, `${possibleType} ${prop}`, label);
propertyKeys(accessorFunc, ["length", "name"], [], label);
} else {
assert_equals(accessorFunc, undefined, `${prop} must not have a ${possibleType}ter`);
}
}
}
function propertyDescriptor(obj, prop, mustMatch) {
const propDesc = Object.getOwnPropertyDescriptor(obj, prop);
for (const key in Object.keys(mustMatch)) {
assert_equals(propDesc[key], mustMatch[key], `${prop} ${key}`);
}
return propDesc;
}
function isConstructorTest(o) {
try {
new (new Proxy(o, {construct: () => ({})}));
return true;
} catch (e) {
return false;
}
}
function formatLabel(label) {
return label !== undefined ? ` ${label}` : "";
}