From: Lady Date: Sat, 21 Oct 2023 19:29:12 +0000 (-0400) Subject: Improve object.js a·p·i X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/8bf43f6e1898ca921d5e63b4513e4c6b2241ebc5 Improve object.js a·p·i - Add tests for functions which were formerly just re·exports of standard library functions. - Test constructibility, name, and length for all exported functions. - Replace `isFrozenObject` and `isSealedObject` with `isUnfrozenObject` and `isUnsealedObject`, as the former versions return _true_, not _false_, when provided with a non·object argument. - Revise `setPrototype` to have better behaviours than the Ecmascript implementation when provided with a non·object argument. --- diff --git a/object.js b/object.js index 55593a3..3115c3b 100644 --- a/object.js +++ b/object.js @@ -403,6 +403,14 @@ export const { PropertyDescriptor } = (() => { })(); export const { + /** + * Defines an own property on the provided object on the provided + * property key using the provided property descriptor. + * + * ※ This is effectively an alias for `Object.defineProperty`. + */ + defineOwnProperty, + /** * Defines own properties on the provided object using the * descriptors on the enumerable own properties of the provided @@ -412,36 +420,13 @@ export const { * multiple source objects. */ defineOwnProperties, -} = (() => { - const { defineProperties } = Object; - const { forEach: arrayForEach } = Array.prototype; - return { - defineOwnProperties: (O, ...sources) => { - call( - arrayForEach, - sources, - [(source) => defineProperties(O, source)], - ); - return O; - }, - }; -})(); - -export const { - /** - * Defines an own property on the provided object on the provided - * property key using the provided property descriptor. - * - * ※ This is an alias for `Object.defineProperty`. - */ - defineProperty: defineOwnProperty, /** * Marks the provided object as non·extensible and marks all its * properties as nonconfigurable and (if data properties) * nonwritable, and returns the object. * - * ※ This is an alias for `Object.freeze`. + * ※ This is effectively an alias for `Object.freeze`. */ freeze, @@ -450,7 +435,8 @@ export const { * provided property key on the provided object, or null if none * exists. * - * ※ This is an alias for `Object.getOwnPropertyDescriptor`. + * ※ This is effectively an alias for + * `Object.getOwnPropertyDescriptor`. */ getOwnPropertyDescriptor, @@ -458,7 +444,8 @@ export const { * Returns the property descriptors for the own properties on the * provided object. * - * ※ This is an alias for `Object.getOwnPropertyDescriptors`. + * ※ This is effectively an alias for + * `Object.getOwnPropertyDescriptors`. */ getOwnPropertyDescriptors, @@ -468,9 +455,9 @@ export const { * * ☡ This includes both enumerable and non·enumerable properties. * - * ※ This is an alias for `Object.getOwnPropertyNames`. + * ※ This is effectively an alias for `Object.getOwnPropertyNames`. */ - getOwnPropertyNames: getOwnPropertyStrings, + getOwnPropertyStrings, /** * Returns an array of symbol‐valued own property keys on the @@ -478,90 +465,97 @@ export const { * * ☡ This includes both enumerable and non·enumerable properties. * - * ※ This is an alias for `Object.getOwnPropertySymbols`. + * ※ This is effectively an alias for + * `Object.getOwnPropertySymbols`. */ getOwnPropertySymbols, /** * Returns the prototype of the provided object. * - * ※ This is an alias for `Object.getPrototypeOf`. + * ※ This is effectively an alias for `Object.getPrototypeOf`. */ - getPrototypeOf: getPrototype, + getPrototype, /** * Returns whether the provided object has an own property with the * provided property key. * - * ※ This is an alias for `Object.hasOwn`. + * ※ This is effectively an alias for `Object.hasOwn`. */ - hasOwn: hasOwnProperty, + hasOwnProperty, /** * Returns whether the provided object is extensible. * - * ※ This is an alias for `Object.isExtensible`. + * ※ This function returns false for nonobjects. + * + * ※ This is effectively an alias for `Object.isExtensible`. */ - isExtensible: isExtensibleObject, + isExtensibleObject, /** * Returns whether the provided object is frozen. * - * ※ This is an alias for `Object.isFrozen`. + * ※ This function returns false for nonobjects. + * + * ※ This is effectively an alias for `!Object.isFrozen`. */ - isFrozen: isFrozenObject, + isUnfrozenObject, /** * Returns whether the provided object is sealed. * - * ※ This is an alias for `Object.isSealed`. + * ※ This function returns false for nonobjects. + * + * ※ This is effectively an alias for `!Object.isSealed`. */ - isSealed: isSealedObject, + isUnsealedObject, /** * Returns an array of key~value pairs for the enumerable, * string‐valued property keys on the provided object. * - * ※ This is an alias for `Object.entries`. + * ※ This is effectively an alias for `Object.entries`. */ - entries: namedEntries, + namedEntries, /** * Returns an array of the enumerable, string‐valued property keys on * the provided object. * - * ※ This is an alias for `Object.keys`. + * ※ This is effectively an alias for `Object.keys`. */ - keys: namedKeys, + namedKeys, /** * Returns an array of property values for the enumerable, * string‐valued property keys on the provided object. * - * ※ This is an alias for `Object.values`. + * ※ This is effectively an alias for `Object.values`. */ - values: namedValues, + namedValues, /** * Returns a new object with the provided prototype and property * descriptors. * - * ※ This is an alias for `Object.create`. + * ※ This is effectively an alias for `Object.create`. */ - create: objectCreate, + objectCreate, /** * Returns a new object with the provided property keys and values. * - * ※ This is an alias for `Object.fromEntries`. + * ※ This is effectively an alias for `Object.fromEntries`. */ - fromEntries: objectFromEntries, + objectFromEntries, /** * Marks the provided object as non·extensible, and returns the * object. * - * ※ This is an alias for `Object.preventExtensions`. + * ※ This is effectively an alias for `Object.preventExtensions`. */ preventExtensions, @@ -569,7 +563,7 @@ export const { * Marks the provided object as non·extensible and marks all its * properties as nonconfigurable, and returns the object. * - * ※ This is an alias for `Object.seal`. + * ※ This is effectively an alias for `Object.seal`. */ seal, @@ -577,18 +571,124 @@ export const { * Sets the values of the enumerable own properties of the provided * additional objects on the provided object. * - * ※ This is an alias for `Object.assign`. + * ※ This is effectively an alias for `Object.assign`. */ - assign: setPropertyValues, + setPropertyValues, /** * Sets the prototype of the provided object to the provided value * and returns the object. * - * ※ This is an alias for `Object.setPrototypeOf`. + * ※ This is effectively an alias for `Object.setPrototypeOf`. + */ + setPrototype, + + /** + * Returns the provided value converted to an object. + * + * Existing objects are returned with no modification. + * + * ☡ This function throws if its argument is null or undefined. */ - setPrototypeOf: setPrototype, -} = Object; + toObject, +} = (() => { + const createObject = Object; + const { + assign, + create, + defineProperty, + defineProperties, + entries, + freeze, + fromEntries, + getOwnPropertyDescriptor, + getOwnPropertyDescriptors, + getOwnPropertyNames, + getOwnPropertySymbols, + getPrototypeOf, + hasOwn, + isExtensible, + isFrozen, + isSealed, + keys, + preventExtensions, + seal, + setPrototypeOf, + values, + } = Object; + const { [ITERATOR]: arrayIterator } = Array.prototype; + const arrayIteratorNext = getPrototypeOf([][ITERATOR]()).next; + const splatIterablePrototype = { + [ITERATOR]() { + return { + next: bind( + arrayIteratorNext, + call(arrayIterator, this.args, []), + [], + ), + }; + }, + }; + const splatIterable = ($) => + create(splatIterablePrototype, { args: { value: $ } }); + + return { + defineOwnProperty: (O, P, Attributes) => + defineProperty(O, P, Attributes), + defineOwnProperties: (O, ...sources) => { + for (const source of splatIterable(sources)) { + defineProperties(O, source); + } + return O; + }, + freeze: (O) => freeze(O), + getOwnPropertyDescriptor: (O, P) => getOwnPropertyDescriptor(O, P), + getOwnPropertyDescriptors: (O) => getOwnPropertyDescriptors(O), + getOwnPropertyStrings: (O) => getOwnPropertyNames(O), + getOwnPropertySymbols: (O) => getOwnPropertySymbols(O), + getPrototype: (O) => getPrototypeOf(O), + hasOwnProperty: (O, P) => hasOwn(O, P), + isExtensibleObject: (O) => isExtensible(O), + isUnfrozenObject: (O) => !isFrozen(O), + isUnsealedObject: (O) => !isSealed(O), + namedEntries: (O) => entries(O), + namedKeys: (O) => keys(O), + namedValues: (O) => values(O), + objectCreate: (O, Properties) => create(O, Properties), + objectFromEntries: (iterable) => fromEntries(iterable), + preventExtensions: (O) => preventExtensions(O), + seal: (O) => seal(O), + setPropertyValues: (target, source, ...sources) => + assign(target, source, ...splatIterable(sources)), + setPrototype: (O, proto) => { + const obj = toObject(O); + if (O === obj) { + // The provided value is an object; set its prototype normally. + return setPrototypeOf(O, proto); + } else { + // The provided value is not an object; attempt to set the + // prototype on a coerced version with extensions prevented, + // then return the provided value. + // + // This will throw if the given prototype does not match the + // existing one on the coerced object. + setPrototypeOf(preventExtensions(obj), proto); + return O; + } + }, + toObject: ($) => { + if ($ == null) { + // The provided value is nullish; this is an error. + throw new TypeError( + `Piscēs: Cannot convert ${$} into an object.`, + ); + } else { + // The provided value is not nullish; coerce it to an object. + return createObject($); + } + }, + }; +})(); export const { /** @@ -796,33 +896,11 @@ export const getMethod = (V, P) => { } }; -/** - * Returns the provided value converted to an object. - * - * Existing objects are returned with no modification. - * - * ☡ This function throws if its argument is null or undefined. - */ -export const { toObject } = (() => { - const makeObject = Object; - return { - toObject: ($) => { - if ($ == null) { - throw new TypeError( - `Piscēs: Cannot convert ${$} into an object.`, - ); - } else { - return makeObject($); - } - }, - }; -})(); - /** * Returns the property key (symbol or string) corresponding to the * provided value. */ export const toPropertyKey = ($) => { const key = toPrimitive($, "string"); - return typeof key == "symbol" ? key : `${key}`; + return typeof key === "symbol" ? key : `${key}`; }; diff --git a/object.test.js b/object.test.js index 68e3264..d5a60bb 100644 --- a/object.test.js +++ b/object.test.js @@ -20,15 +20,35 @@ import { } from "./dev-deps.js"; import { defineOwnProperties, + defineOwnProperty, deleteOwnProperty, + freeze, frozenCopy, getMethod, + getOwnPropertyDescriptor, + getOwnPropertyDescriptors, getOwnPropertyKeys, + getOwnPropertyStrings, + getOwnPropertySymbols, getPropertyValue, + getPrototype, + hasOwnProperty, hasProperty, + isExtensibleObject, + isUnfrozenObject, + isUnsealedObject, LazyLoader, + namedEntries, + namedKeys, + namedValues, + objectCreate, + objectFromEntries, + preventExtensions, PropertyDescriptor, + seal, setPropertyValue, + setPropertyValues, + setPrototype, toObject, toPropertyKey, } from "./object.js"; @@ -80,6 +100,10 @@ describe("LazyLoader", () => { }, ); + it("[[Call]] throws an error", () => { + assertThrows(() => LazyLoader({})); + }); + it("[[Construct]] creates a new object which inherits from the correct prototype", () => { assertStrictEquals( Object.getPrototypeOf(new LazyLoader(methodsObject)), @@ -269,9 +293,25 @@ describe("LazyLoader", () => { }, ); }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(LazyLoader.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(LazyLoader.name, "LazyLoader"); + }); + }); }); describe("PropertyDescriptor", () => { + it("[[Call]] throws an error", () => { + assertThrows(() => PropertyDescriptor({})); + }); + it("[[Construct]] creates a new PropertyDescriptor", () => { assertStrictEquals( Object.getPrototypeOf(new PropertyDescriptor({})), @@ -283,6 +323,21 @@ describe("PropertyDescriptor", () => { assertThrows(() => new PropertyDescriptor("failure")); }); + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(PropertyDescriptor.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + PropertyDescriptor.name, + "PropertyDescriptor", + ); + }); + }); + describe("::complete", () => { it("[[Call]] completes a generic descriptor", () => { const desc = {}; @@ -316,6 +371,24 @@ describe("PropertyDescriptor", () => { set: undefined, }); }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + PropertyDescriptor.prototype.complete.length, + 0, + ); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + PropertyDescriptor.prototype.complete.name, + "complete", + ); + }); + }); }); describe("::isAccessorDescriptor", () => { @@ -351,6 +424,30 @@ describe("PropertyDescriptor", () => { true, ); }); + + describe("[[GetOwnProperty]].get.length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + PropertyDescriptor.prototype, + "isAccessorDescriptor", + ).get.length, + 0, + ); + }); + }); + + describe("[[GetOwnProperty]].get.name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + PropertyDescriptor.prototype, + "isAccessorDescriptor", + ).get.name, + "get isAccessorDescriptor", + ); + }); + }); }); describe("::isDataDescriptor", () => { @@ -386,6 +483,30 @@ describe("PropertyDescriptor", () => { false, ); }); + + describe("[[GetOwnProperty]].get.length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + PropertyDescriptor.prototype, + "isDataDescriptor", + ).get.length, + 0, + ); + }); + }); + + describe("[[GetOwnProperty]].get.name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + PropertyDescriptor.prototype, + "isDataDescriptor", + ).get.name, + "get isDataDescriptor", + ); + }); + }); }); describe("::isFullyPopulated", () => { @@ -445,6 +566,30 @@ describe("PropertyDescriptor", () => { true, ); }); + + describe("[[GetOwnProperty]].get.length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + PropertyDescriptor.prototype, + "isFullyPopulated", + ).get.length, + 0, + ); + }); + }); + + describe("[[GetOwnProperty]].get.name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + PropertyDescriptor.prototype, + "isFullyPopulated", + ).get.name, + "get isFullyPopulated", + ); + }); + }); }); describe("::isGenericDescriptor", () => { @@ -480,6 +625,30 @@ describe("PropertyDescriptor", () => { false, ); }); + + describe("[[GetOwnProperty]].get.length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + PropertyDescriptor.prototype, + "isGenericDescriptor", + ).get.length, + 0, + ); + }); + }); + + describe("[[GetOwnProperty]].get.name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + Object.getOwnPropertyDescriptor( + PropertyDescriptor.prototype, + "isGenericDescriptor", + ).get.name, + "get isGenericDescriptor", + ); + }); + }); }); describe("~configurable", () => { @@ -711,6 +880,38 @@ describe("PropertyDescriptor", () => { }); }); +describe("defineOwnProperty", () => { + it("[[Call]] defines the property", () => { + const obj = {}; + defineOwnProperty(obj, "etaoin", {}); + assert("etaoin" in obj); + }); + + it("[[Call]] returns the provided object", () => { + const obj = {}; + assertStrictEquals(defineOwnProperty(obj, "etaoin", {}), obj); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new defineOwnProperty(obj, "etaoin", {})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(defineOwnProperty.length, 3); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + defineOwnProperty.name, + "defineOwnProperty", + ); + }); + }); +}); + describe("defineOwnProperties", () => { it("[[Call]] defines properties from the provided objects", () => { const obj = {}; @@ -737,6 +938,25 @@ describe("defineOwnProperties", () => { const obj = {}; assertStrictEquals(defineOwnProperties(obj), obj); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new defineOwnProperties({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(defineOwnProperties.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + defineOwnProperties.name, + "defineOwnProperties", + ); + }); + }); }); describe("deleteOwnProperty", () => { @@ -761,6 +981,51 @@ describe("deleteOwnProperty", () => { const obj = {}; assertStrictEquals(deleteOwnProperty(obj, ""), obj); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new deleteOwnProperty({}, "")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(deleteOwnProperty.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(deleteOwnProperty.name, "deleteOwnProperty"); + }); + }); +}); + +describe("freeze", () => { + it("[[Call]] freezes the object", () => { + const obj = {}; + freeze(obj); + assert(Object.isFrozen(obj)); + }); + + it("[[Call]] returns the provided object", () => { + const obj = {}; + assertStrictEquals(freeze(obj), obj); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new freeze({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(freeze.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(freeze.name, "freeze"); + }); + }); }); describe("frozenCopy", () => { @@ -877,6 +1142,22 @@ describe("frozenCopy", () => { null, ); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new frozenCopy({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(frozenCopy.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(frozenCopy.name, "frozenCopy"); + }); + }); }); describe("getMethod", () => { @@ -897,6 +1178,103 @@ describe("getMethod", () => { it("[[Call]] throws if the resulting value isn’t callable", () => { assertThrows(() => getMethod({ "failure": true }, "failure")); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new getMethod({ method() {} }, "method")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getMethod.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(getMethod.name, "getMethod"); + }); + }); +}); + +describe("getOwnPropertyDescriptor", () => { + it("[[Call]] gets the descriptor", () => { + assertEquals( + getOwnPropertyDescriptor({ success: true }, "success"), + { + configurable: true, + enumerable: true, + value: true, + writable: true, + }, + ); + }); + + it("[[Call]] returns undefined for non‐own properties", () => { + assertStrictEquals( + getOwnPropertyDescriptor({}, "valueOf"), + undefined, + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new getOwnPropertyDescriptor({}, "")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getOwnPropertyDescriptor.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + getOwnPropertyDescriptor.name, + "getOwnPropertyDescriptor", + ); + }); + }); +}); + +describe("getOwnPropertyDescriptors", () => { + it("[[Call]] gets the descriptors", () => { + assertEquals( + getOwnPropertyDescriptors({ success: true, etaoin: "shrdlu" }), + { + success: { + configurable: true, + enumerable: true, + value: true, + writable: true, + }, + etaoin: { + configurable: true, + enumerable: true, + value: "shrdlu", + writable: true, + }, + }, + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new getOwnPropertyDescriptors({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getOwnPropertyDescriptors.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + getOwnPropertyDescriptors.name, + "getOwnPropertyDescriptors", + ); + }); + }); }); describe("getOwnPropertyKeys", () => { @@ -912,6 +1290,101 @@ describe("getOwnPropertyKeys", () => { assertThrows(() => getOwnPropertyKeys(null)); assertThrows(() => getOwnPropertyKeys(undefined)); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new getOwnPropertyKeys({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getOwnPropertyKeys.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + getOwnPropertyKeys.name, + "getOwnPropertyKeys", + ); + }); + }); +}); + +describe("getOwnPropertyStrings", () => { + it("[[Call]] gets own string keys", () => { + assertEquals(getOwnPropertyStrings({ success: true }), [ + "success", + ]); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertEquals(getOwnPropertyStrings("foo"), [ + "0", + "1", + "2", + "length", + ]); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => getOwnPropertyStrings(null)); + assertThrows(() => getOwnPropertyStrings(undefined)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new getOwnPropertyStrings({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getOwnPropertyStrings.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + getOwnPropertyStrings.name, + "getOwnPropertyStrings", + ); + }); + }); +}); + +describe("getOwnPropertySymbols", () => { + it("[[Call]] gets own symbol keys", () => { + const sym = Symbol(); + assertEquals(getOwnPropertySymbols({ [sym]: true }), [sym]); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertEquals(getOwnPropertySymbols("foo"), []); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => getOwnPropertySymbols(null)); + assertThrows(() => getOwnPropertySymbols(undefined)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new getOwnPropertySymbols({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getOwnPropertySymbols.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + getOwnPropertySymbols.name, + "getOwnPropertySymbols", + ); + }); + }); }); describe("getPropertyValue", () => { @@ -933,6 +1406,60 @@ describe("getPropertyValue", () => { assertThrows(() => getPropertyValue(null, "valueOf")); assertThrows(() => getPropertyValue(undefined, "valueOf")); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new getPropertyValue({}, "valueOf")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getPropertyValue.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(getPropertyValue.name, "getPropertyValue"); + }); + }); +}); + +describe("getPrototype", () => { + it("[[Call]] gets object prototypes", () => { + assertStrictEquals(getPrototype({}), Object.prototype); + const proto = {}; + assertStrictEquals(getPrototype(Object.create(proto)), proto); + }); + + it("[[Call]] gets null prototypes", () => { + assertStrictEquals(getPrototype(Object.create(null)), null); + }); + + it("[[Call]] gets prototypes for coercible primitives", () => { + assertStrictEquals(getPrototype(1), Number.prototype); + assertStrictEquals(getPrototype(Symbol()), Symbol.prototype); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => getPrototype(null)); + assertThrows(() => getPrototype(undefined)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new getPrototype({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getPrototype.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(getPrototype.name, "getPrototype"); + }); + }); }); describe("hasProperty", () => { @@ -941,9 +1468,11 @@ describe("hasProperty", () => { hasProperty({ success: "etaoin" }, "success"), true, ); + assertStrictEquals(hasProperty({}, "hasOwnProperty"), true); }); it("[[Call]] works for values coercible to objects", () => { + assertStrictEquals(hasProperty("", "length"), true); assertStrictEquals(hasProperty("", "toString"), true); }); @@ -951,23 +1480,458 @@ describe("hasProperty", () => { assertThrows(() => hasProperty(null, "valueOf")); assertThrows(() => hasProperty(undefined, "valueOf")); }); -}); -describe("setPropertyValue", () => { - it("[[Call]] sets the provided property on the provided object", () => { - const obj = {}; - setPropertyValue(obj, "success", true); - assertStrictEquals(obj.success, true); + it("[[Construct]] throws an error", () => { + assertThrows(() => new hasProperty({}, "valueOf")); }); - it("[[Call]] calls setters", () => { - const setter = spy((_) => {}); - const obj = Object.create(null, { success: { set: setter } }); - setPropertyValue(obj, "success", true); - assertSpyCalls(setter, 1); - assertSpyCall(setter, 0, { - args: [true], - self: obj, + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(hasProperty.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(hasProperty.name, "hasProperty"); + }); + }); +}); + +describe("hasOwnProperty", () => { + it("[[Call]] gets whether an own property exists on the provided object", () => { + assertStrictEquals( + hasOwnProperty({ success: "etaoin" }, "success"), + true, + ); + assertStrictEquals(hasOwnProperty({}, "hasOwnProperty"), false); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertStrictEquals(hasOwnProperty("", "length"), true); + assertStrictEquals(hasOwnProperty("", "toString"), false); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => hasOwnProperty(null, "valueOf")); + assertThrows(() => hasOwnProperty(undefined, "valueOf")); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new hasOwnProperty({}, "valueOf")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(hasOwnProperty.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(hasOwnProperty.name, "hasOwnProperty"); + }); + }); +}); + +describe("isExtensibleObject", () => { + it("[[Call]] returns true for extensible objects", () => { + assertStrictEquals(isExtensibleObject({}), true); + }); + + it("[[Call]] returns false for coercible primitives", () => { + assertStrictEquals(isExtensibleObject(1), false); + assertStrictEquals(isExtensibleObject(Symbol()), false); + }); + + it("[[Call]] returns false for non·extensible objects", () => { + assertStrictEquals( + isExtensibleObject(Object.preventExtensions({})), + false, + ); + }); + + it("[[Call]] returns false for null and undefined", () => { + assertStrictEquals(isExtensibleObject(null), false); + assertStrictEquals(isExtensibleObject(undefined), false); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new isExtensibleObject({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(isExtensibleObject.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + isExtensibleObject.name, + "isExtensibleObject", + ); + }); + }); +}); + +describe("isUnfrozenObject", () => { + it("[[Call]] returns true for unfrozen objects", () => { + assertStrictEquals(isUnfrozenObject({}), true); + }); + + it("[[Call]] returns false for coercible primitives", () => { + assertStrictEquals(isUnfrozenObject(1), false); + assertStrictEquals(isUnfrozenObject(Symbol()), false); + }); + + it("[[Call]] returns false for frozen objects", () => { + assertStrictEquals(isUnfrozenObject(Object.freeze({})), false); + }); + + it("[[Call]] returns false for null and undefined", () => { + assertStrictEquals(isUnfrozenObject(null), false); + assertStrictEquals(isUnfrozenObject(undefined), false); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new isUnfrozenObject({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(isUnfrozenObject.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(isUnfrozenObject.name, "isUnfrozenObject"); + }); + }); +}); + +describe("isUnsealedObject", () => { + it("[[Call]] returns true for unsealed objects", () => { + assertStrictEquals(isUnsealedObject({}), true); + }); + + it("[[Call]] returns false for coercible primitives", () => { + assertStrictEquals(isUnsealedObject(1), false); + assertStrictEquals(isUnsealedObject(Symbol()), false); + }); + + it("[[Call]] returns false for sealed objects", () => { + assertStrictEquals(isUnsealedObject(Object.seal({})), false); + }); + + it("[[Call]] returns false for null and undefined", () => { + assertStrictEquals(isUnsealedObject(null), false); + assertStrictEquals(isUnsealedObject(undefined), false); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new isUnsealedObject({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(isUnsealedObject.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(isUnsealedObject.name, "isUnsealedObject"); + }); + }); +}); + +describe("namedEntries", () => { + it("[[Call]] gets named entries", () => { + assertEquals(namedEntries({ success: true }), [["success", true]]); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertEquals(namedEntries("foo"), [ + ["0", "f"], + ["1", "o"], + ["2", "o"], + ]); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => namedEntries(null)); + assertThrows(() => namedEntries(undefined)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new namedEntries({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(namedEntries.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(namedEntries.name, "namedEntries"); + }); + }); +}); + +describe("namedKeys", () => { + it("[[Call]] gets named keys", () => { + assertEquals(namedKeys({ success: true }), ["success"]); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertEquals(namedKeys("foo"), [ + "0", + "1", + "2", + ]); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => namedKeys(null)); + assertThrows(() => namedKeys(undefined)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new namedKeys({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(namedKeys.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(namedKeys.name, "namedKeys"); + }); + }); +}); + +describe("namedValues", () => { + it("[[Call]] gets named values", () => { + assertEquals(namedValues({ success: true }), [true]); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertEquals(namedValues("foo"), [ + "f", + "o", + "o", + ]); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => namedValues(null)); + assertThrows(() => namedValues(undefined)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new namedValues({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(namedValues.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(namedValues.name, "namedValues"); + }); + }); +}); + +describe("objectCreate", () => { + it("[[Call]] creates an object", () => { + const obj = objectCreate(null); + assertStrictEquals(Object(obj), obj); + }); + + it("[[Call]] correctly sets the prototype", () => { + const proto = {}; + assertStrictEquals( + Object.getPrototypeOf(objectCreate(proto)), + proto, + ); + assertStrictEquals( + Object.getPrototypeOf(objectCreate(null)), + null, + ); + }); + + it("[[Call]] correctly sets own properties", () => { + assertEquals( + Object.getOwnPropertyDescriptors( + objectCreate(null, { success: { value: true } }), + ), + { + success: { + configurable: false, + enumerable: false, + value: true, + writable: false, + }, + }, + ); + }); + + it("[[Call]] throws for coercible primitives", () => { + assertThrows(() => objectCreate(1)); + assertThrows(() => objectCreate(Symbol())); + }); + + it("[[Call]] throws for undefined", () => { + assertThrows(() => objectCreate(undefined)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new objectCreate({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(objectCreate.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(objectCreate.name, "objectCreate"); + }); + }); +}); + +describe("objectFromEntries", () => { + it("[[Call]] creates an object", () => { + const obj = objectFromEntries([]); + assertStrictEquals(Object(obj), obj); + }); + + it("[[Call]] correctly sets the prototype", () => { + assertStrictEquals( + Object.getPrototypeOf(objectFromEntries([])), + Object.prototype, + ); + }); + + it("[[Call]] correctly sets own properties", () => { + assertEquals( + Object.entries(objectFromEntries([["success", true]])), + [["success", true]], + ); + }); + + it("[[Call]] throws if the argument is not a nested arraylike", () => { + assertThrows(() => objectFromEntries(1)); + assertThrows(() => objectFromEntries(Symbol())); + assertThrows(() => objectFromEntries(null)); + assertThrows(() => objectFromEntries(undefined)); + assertThrows(() => objectFromEntries({})); + assertThrows(() => objectFromEntries([undefined])); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new objectFromEntries([])); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(objectFromEntries.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(objectFromEntries.name, "objectFromEntries"); + }); + }); +}); + +describe("preventExtensions", () => { + it("[[Call]] prevents extensions on the object", () => { + const obj = {}; + preventExtensions(obj); + assert(!Object.isExtensible(obj)); + }); + + it("[[Call]] returns the provided object", () => { + const obj = {}; + assertStrictEquals(preventExtensions(obj), obj); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new preventExtensions({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(preventExtensions.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(preventExtensions.name, "preventExtensions"); + }); + }); +}); + +describe("seal", () => { + it("[[Call]] seals the object", () => { + const obj = {}; + seal(obj); + assert(Object.isSealed(obj)); + }); + + it("[[Call]] returns the provided object", () => { + const obj = {}; + assertStrictEquals(seal(obj), obj); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new seal({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(seal.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(seal.name, "seal"); + }); + }); +}); + +describe("setPropertyValue", () => { + it("[[Call]] sets the provided property on the provided object", () => { + const obj = {}; + setPropertyValue(obj, "success", true); + assertStrictEquals(obj.success, true); + }); + + it("[[Call]] calls setters", () => { + const setter = spy((_) => {}); + const obj = Object.create(null, { success: { set: setter } }); + setPropertyValue(obj, "success", true); + assertSpyCalls(setter, 1); + assertSpyCall(setter, 0, { + args: [true], + self: obj, }); }); @@ -1005,6 +1969,165 @@ describe("setPropertyValue", () => { const obj = {}; assertStrictEquals(setPropertyValue(obj, "", undefined), obj); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new setPropertyValue({}, "", undefined)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(setPropertyValue.length, 3); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(setPropertyValue.name, "setPropertyValue"); + }); + }); +}); + +describe("setPropertyValues", () => { + it("[[Call]] sets the provided properties on the provided object", () => { + const obj = {}; + setPropertyValues(obj, { success: true, all: "good" }); + assertStrictEquals(obj.success, true); + assertStrictEquals(obj.all, "good"); + }); + + it("[[Call]] can take multiple objects", () => { + const obj = {}; + setPropertyValues( + obj, + { success: false, all: "good" }, + { success: true }, + ); + assertStrictEquals(obj.success, true); + assertStrictEquals(obj.all, "good"); + }); + + it("[[Call]] ignores nullish arguments", () => { + const obj = {}; + setPropertyValues(obj, null, undefined, { success: true }); + assertStrictEquals(obj.success, true); + }); + + it("[[Call]] calls setters", () => { + const setter = spy((_) => {}); + const obj = Object.create(null, { success: { set: setter } }); + setPropertyValues(obj, { success: true }); + assertSpyCalls(setter, 1); + assertSpyCall(setter, 0, { + args: [true], + self: obj, + }); + }); + + it("[[Call]] calls setters multiple times if property appears more than once", () => { + const setter = spy((_) => {}); + const obj = Object.create(null, { success: { set: setter } }); + setPropertyValues(obj, { success: false }, { success: true }); + assertSpyCalls(setter, 2); + assertSpyCall(setter, 0, { + args: [false], + self: obj, + }); + assertSpyCall(setter, 1, { + args: [true], + self: obj, + }); + }); + + it("[[Call]] walks the prototype chain", () => { + const setter = spy((_) => {}); + const obj = Object.create( + Object.create(null, { success: { set: setter } }), + ); + setPropertyValues(obj, { success: true }); + assertSpyCalls(setter, 1); + assertSpyCall(setter, 0, { + args: [true], + self: obj, + }); + }); + + it("[[Call]] throws if the property can’t be set", () => { + const obj = Object.freeze({ failure: undefined }); + assertThrows(() => setPropertyValues(obj, { failure: true })); + }); + + it("[[Call]] returns the provided object", () => { + const obj = {}; + assertStrictEquals(setPropertyValues(obj, { "": undefined }), obj); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new setPropertyValues(obj, { "": undefined })); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(setPropertyValues.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(setPropertyValues.name, "setPropertyValues"); + }); + }); +}); + +describe("setPrototype", () => { + it("[[Call]] sets object prototypes", () => { + const obj = {}; + const proto = {}; + setPrototype(obj, proto); + assertStrictEquals(Object.getPrototypeOf(obj), proto); + }); + + it("[[Call]] sets null prototypes", () => { + const obj = {}; + setPrototype(obj, null); + assertStrictEquals(Object.getPrototypeOf(obj), null); + }); + + it("[[Call]] can set coercible primitives to their same prototype", () => { + setPrototype(1, Number.prototype); + setPrototype(Symbol(), Symbol.prototype); + }); + + it("[[Call]] throws when setting coercible primitives to a different prototype", () => { + assertThrows(() => setPrototype(1, Object.prototype)); + assertThrows(() => setPrototype(Symbol(), Object.prototype)); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => setPrototype(null, Object.prototype)); + assertThrows(() => setPrototype(undefined, Object.prototype)); + }); + + it("[[Call]] returns the provided value", () => { + const obj = {}; + assertStrictEquals(setPrototype(obj, null), obj); + assertStrictEquals(setPrototype(1, Number.prototype), 1); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new setPrototype({}, null)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(setPrototype.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(setPrototype.name, "setPrototype"); + }); + }); }); describe("toObject", () => { @@ -1023,6 +2146,22 @@ describe("toObject", () => { assertStrictEquals(typeof toObject(sym), "object"); assertStrictEquals(toObject(sym).valueOf(), sym); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new toObject({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(toObject.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(toObject.name, "toObject"); + }); + }); }); describe("toPropertyKey", () => { @@ -1048,4 +2187,20 @@ describe("toPropertyKey", () => { "success", ); }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new toPropertyKey("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(toPropertyKey.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(toPropertyKey.name, "toPropertyKey"); + }); + }); });