X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/7c3a2eda590637af9463e5a59ab798f802d274a9..9c6be9646631101a3883d4dac08495d5e35e9977:/object.test.js diff --git a/object.test.js b/object.test.js index da890d3..cba44b9 100644 --- a/object.test.js +++ b/object.test.js @@ -1,7 +1,7 @@ // ♓🌟 Piscēs ∷ object.test.js // ==================================================================== // -// Copyright © 2022 Lady [@ Lady’s Computer]. +// Copyright © 2022–2023 Lady [@ Lady’s Computer]. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -10,6 +10,7 @@ import { assert, assertEquals, + assertNotStrictEquals, assertSpyCall, assertSpyCalls, assertStrictEquals, @@ -19,248 +20,2146 @@ import { spy, } from "./dev-deps.js"; import { + defineOwnDataProperty, + defineOwnNonenumerableDataProperty, defineOwnProperties, + defineOwnProperty, deleteOwnProperty, + freeze, frozenCopy, - PropertyDescriptor, + getMethod, + getOwnPropertyDescriptor, + getOwnPropertyDescriptors, + getOwnPropertyEntries, + getOwnPropertyKeys, + getOwnPropertyStrings, + getOwnPropertySymbols, + getOwnPropertyValue, + getOwnPropertyValues, + getPropertyValue, + getPrototype, + hasOwnProperty, + hasProperty, + isArraylikeObject, + isConcatSpreadableObject, + isExtensibleObject, + isPropertyDescriptorRecord, + isUnfrozenObject, + isUnsealedObject, + LazyLoader, + lengthOfArraylike, + namedEntries, + namedKeys, + namedValues, + objectCreate, + objectFromEntries, + preventExtensions, + seal, setPropertyValue, + setPropertyValues, + setPrototype, toObject, - toPropertyKey, + toPropertyDescriptorRecord, } from "./object.js"; -describe("PropertyDescriptor", () => { - it("[[Construct]] creates a new PropertyDescriptor", () => { +describe("LazyLoader", () => { + const symbol = Symbol("foo"); + const prototype = {}; + const etaoinMethod = spy(() => "success"); + const shrdluMethod = spy(() => "success"); + const cmfwypMethod = spy(() => "success"); + const vbgkqjMethod = spy(() => "success"); + const methodsObject = Object.create( + prototype, + { + etaoin: { + configurable: false, + enumerable: true, + value: etaoinMethod, + writable: false, + }, + shrdlu: { + configurable: true, + enumerable: false, + value: shrdluMethod, + writable: false, + }, + cmfwyp: { + configurable: true, + enumerable: false, + get() { + return cmfwypMethod; + }, + }, + vbgkqj: { + configurable: false, + enumerable: true, + get() { + return vbgkqjMethod; + }, + set(_) {}, + }, + xzfiflffffi: { configurable: true, enumerable: false, set(_) {} }, + [symbol]: { + configurable: true, + enumerable: false, + value: "failure", + writable: true, + }, + }, + ); + + it("[[Call]] throws an error", () => { + assertThrows(() => LazyLoader({})); + }); + + it("[[Construct]] creates a new object which inherits from the correct prototype", () => { assertStrictEquals( - Object.getPrototypeOf(new PropertyDescriptor({})), - PropertyDescriptor.prototype, + Object.getPrototypeOf(new LazyLoader(methodsObject)), + prototype, ); }); - it("[[Construct]] throws for primitives", () => { - assertThrows(() => new PropertyDescriptor("failure")); + it("[[Construct]] creates a new object with the desired properties", () => { + assertEquals( + Reflect.ownKeys(new LazyLoader(methodsObject)), + ["etaoin", "shrdlu", "cmfwyp", "vbgkqj", "xzfiflffffi", symbol], + ); + }); + + it("[[Construct]] creates a new object with configurable properties", () => { + assertEquals( + Object.fromEntries( + function* (ll) { + for (const key of Reflect.ownKeys(ll)) { + yield [ + key, + Object.getOwnPropertyDescriptor(ll, key).configurable, + ]; + } + }(new LazyLoader(methodsObject)), + ), + { + etaoin: true, + shrdlu: true, + cmfwyp: true, + vbgkqj: true, + xzfiflffffi: true, + [symbol]: true, + }, + ); + }); + + it("[[Construct]] creates a new object with the correct enumerability", () => { + assertEquals( + Object.fromEntries( + function* (ll) { + for (const key of Reflect.ownKeys(ll)) { + yield [ + key, + Object.getOwnPropertyDescriptor(ll, key).enumerable, + ]; + } + }(new LazyLoader(methodsObject)), + ), + { + etaoin: true, + shrdlu: false, + cmfwyp: false, + vbgkqj: true, + xzfiflffffi: false, + [symbol]: false, + }, + ); + }); + + it("[[Construct]] creates a new object with defined getters", () => { + assertEquals( + Object.fromEntries( + function* (ll) { + for (const key of Reflect.ownKeys(ll)) { + yield [ + key, + Object.getOwnPropertyDescriptor(ll, key).get?.name, + ]; + } + }(new LazyLoader(methodsObject)), + ), + { + etaoin: "get etaoin", + shrdlu: "get shrdlu", + cmfwyp: "get cmfwyp", + vbgkqj: "get vbgkqj", + xzfiflffffi: "get xzfiflffffi", + [symbol]: `get [${symbol.description}]`, + }, + ); }); - describe("::complete", () => { - it("[[Call]] completes a generic descriptor", () => { - const desc = {}; - PropertyDescriptor.prototype.complete.call(desc); - assertEquals(desc, { + it("[[Construct]] creates a new object with defined setters for writable properties only", () => { + assertEquals( + Object.fromEntries( + function* (ll) { + for (const key of Reflect.ownKeys(ll)) { + yield [ + key, + Object.getOwnPropertyDescriptor(ll, key).set?.name, + ]; + } + }(new LazyLoader(methodsObject)), + ), + { + etaoin: undefined, + shrdlu: undefined, + cmfwyp: undefined, + vbgkqj: undefined, + xzfiflffffi: undefined, + [symbol]: `set [${symbol.description}]`, + }, + ); + }); + + it("[[Construct]] creates a new object with correct getter behaviour", () => { + const ll = new LazyLoader(methodsObject); + ll.etaoin; + assertEquals( + Object.getOwnPropertyDescriptor(ll, "etaoin"), + { configurable: false, + enumerable: true, + value: "success", + writable: false, + }, + ); + assertSpyCalls(etaoinMethod, 1); + assertSpyCall(etaoinMethod, 0, { + args: [], + self: ll, + returned: "success", + }); + ll.shrdlu; + assertEquals( + Object.getOwnPropertyDescriptor(ll, "shrdlu"), + { + configurable: true, enumerable: false, - value: undefined, + value: "success", writable: false, - }); + }, + ); + assertSpyCalls(shrdluMethod, 1); + assertSpyCall(shrdluMethod, 0, { + args: [], + self: ll, + returned: "success", }); - - it("[[Call]] completes a data descriptor", () => { - const desc = { value: undefined }; - PropertyDescriptor.prototype.complete.call(desc); - assertEquals(desc, { - configurable: false, + ll.cmfwyp; + assertEquals( + Object.getOwnPropertyDescriptor(ll, "cmfwyp"), + { + configurable: true, enumerable: false, - value: undefined, + value: "success", writable: false, - }); + }, + ); + assertSpyCalls(cmfwypMethod, 1); + assertSpyCall(cmfwypMethod, 0, { + args: [], + self: ll, + returned: "success", }); - - it("[[Call]] completes an accessor descriptor", () => { - const desc = { get: undefined }; - PropertyDescriptor.prototype.complete.call(desc); - assertEquals(desc, { + ll.vbgkqj; + assertEquals( + Object.getOwnPropertyDescriptor(ll, "vbgkqj"), + { configurable: false, + enumerable: true, + value: "success", + writable: false, + }, + ); + assertSpyCalls(vbgkqjMethod, 1); + assertSpyCall(vbgkqjMethod, 0, { + args: [], + self: ll, + returned: "success", + }); + assertThrows(() => ll.xzfiflffffi); + assertThrows(() => ll[symbol]); + }); + + it("[[Construct]] creates a new object with correct setter behaviour", () => { + const ll = new LazyLoader(methodsObject); + ll[symbol] = "success"; + assertEquals( + Object.getOwnPropertyDescriptor(ll, symbol), + { + configurable: true, enumerable: false, - get: undefined, - set: undefined, - }); + value: "success", + writable: true, + }, + ); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(LazyLoader.length, 1); }); }); - describe("::isAccessorDescriptor", () => { - it("[[Get]] returns false for a generic descriptor", () => { - assertStrictEquals( - Reflect.get( - PropertyDescriptor.prototype, - "isAccessorDescriptor", - {}, - ), - false, - ); + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(LazyLoader.name, "LazyLoader"); }); + }); +}); - it("[[Get]] returns false for a data descriptor", () => { - assertStrictEquals( - Reflect.get( - PropertyDescriptor.prototype, - "isAccessorDescriptor", - { value: undefined }, - ), - false, - ); +describe("defineOwnDataProperty", () => { + it("[[Call]] defines the property", () => { + const obj = {}; + defineOwnDataProperty(obj, "etaoin", "success"); + assert(Object.hasOwn(obj, "etaoin")); + assertStrictEquals(obj.etaoin, "success"); + }); + + it("[[Call]] defines a configurable, enumerable, writable property", () => { + const obj = {}; + defineOwnDataProperty(obj, "etaoin", "success"); + assertEquals( + Object.getOwnPropertyDescriptor(obj, "etaoin"), + { + configurable: true, + enumerable: true, + value: "success", + writable: true, + }, + ); + }); + + it("[[Call]] returns the provided object", () => { + const obj = {}; + assertStrictEquals( + defineOwnDataProperty(obj, "etaoin", null), + obj, + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new defineOwnDataProperty(obj, "etaoin", null)); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(defineOwnDataProperty.length, 3); }); + }); - it("[[Get]] returns true for an accessor descriptor", () => { + describe(".name", () => { + it("[[Get]] returns the correct name", () => { assertStrictEquals( - Reflect.get( - PropertyDescriptor.prototype, - "isAccessorDescriptor", - { get: undefined }, - ), - true, + defineOwnDataProperty.name, + "defineOwnDataProperty", ); }); }); +}); - describe("::isDataDescriptor", () => { - it("[[Get]] returns false for a generic descriptor", () => { - assertStrictEquals( - Reflect.get( - PropertyDescriptor.prototype, - "isDataDescriptor", - {}, - ), - false, - ); +describe("defineOwnNonenumerableDataProperty", () => { + it("[[Call]] defines the property", () => { + const obj = {}; + defineOwnNonenumerableDataProperty(obj, "etaoin", "success"); + assert(Object.hasOwn(obj, "etaoin")); + assertStrictEquals(obj.etaoin, "success"); + }); + + it("[[Call]] defines a configurable, non·enumerable, writable property", () => { + const obj = {}; + defineOwnNonenumerableDataProperty(obj, "etaoin", "success"); + assertEquals( + Object.getOwnPropertyDescriptor(obj, "etaoin"), + { + configurable: true, + enumerable: false, + value: "success", + writable: true, + }, + ); + }); + + it("[[Call]] returns the provided object", () => { + const obj = {}; + assertStrictEquals( + defineOwnNonenumerableDataProperty(obj, "etaoin", null), + obj, + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => + new defineOwnNonenumerableDataProperty(obj, "etaoin", null) + ); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(defineOwnNonenumerableDataProperty.length, 3); }); + }); - it("[[Get]] returns true for a data descriptor", () => { + describe(".name", () => { + it("[[Get]] returns the correct name", () => { assertStrictEquals( - Reflect.get( - PropertyDescriptor.prototype, - "isDataDescriptor", - { value: undefined }, - ), - true, + defineOwnNonenumerableDataProperty.name, + "defineOwnNonenumerableDataProperty", ); }); + }); +}); + +describe("defineOwnProperty", () => { + it("[[Call]] defines the property", () => { + const obj = {}; + defineOwnProperty(obj, "etaoin", {}); + assert(Object.hasOwn(obj, "etaoin")); + }); + + 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); + }); + }); - it("[[Get]] returns false for an accessor descriptor", () => { + describe(".name", () => { + it("[[Get]] returns the correct name", () => { assertStrictEquals( - Reflect.get( - PropertyDescriptor.prototype, - "isDataDescriptor", - { get: undefined }, - ), - false, + defineOwnProperty.name, + "defineOwnProperty", ); }); }); +}); - describe("::isFullyPopulated", () => { - it("[[Get]] returns false for a generic descriptor", () => { - assertStrictEquals( - Reflect.get( - PropertyDescriptor.prototype, - "isFullyPopulated", - {}, - ), - false, - ); +describe("defineOwnProperties", () => { + it("[[Call]] defines properties from the provided objects", () => { + const obj = {}; + defineOwnProperties(obj, { + etaoin: {}, + shrdlu: {}, + }, { cmfwyp: {} }); + assert("etaoin" in obj); + assert("shrdlu" in obj); + assert("cmfwyp" in obj); + }); + + it("[[Call]] overrides earlier declarations with later ones", () => { + const obj = { etaoin: undefined }; + defineOwnProperties(obj, { + etaoin: { value: "failure" }, + }, { + etaoin: { value: "success" }, }); + assertStrictEquals(obj.etaoin, "success"); + }); - it("[[Get]] returns false for a non‐fully‐populated data descriptor", () => { - assertStrictEquals( - Reflect.get( - PropertyDescriptor.prototype, - "isFullyPopulated", - { value: undefined }, - ), - false, - ); + it("[[Call]] returns the provided object", () => { + 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); }); + }); - it("[[Get]] returns true for a fully‐populated data descriptor", () => { + describe(".name", () => { + it("[[Get]] returns the correct name", () => { assertStrictEquals( - Reflect.get(PropertyDescriptor.prototype, "isFullyPopulated", { - configurable: true, - enumerable: true, - value: undefined, - writable: true, - }), - true, + defineOwnProperties.name, + "defineOwnProperties", ); }); + }); +}); - it("[[Get]] returns false for a non‐fully‐populated accessor descriptor", () => { - assertStrictEquals( - Reflect.get( - PropertyDescriptor.prototype, - "isFullyPopulated", - { get: undefined }, - ), - false, - ); +describe("deleteOwnProperty", () => { + it("[[Call]] deletes the provided property on the provided object", () => { + const obj = { failure: undefined }; + deleteOwnProperty(obj, "failure"); + assert(!("failure" in obj)); + }); + + it("[[Call]] does nothing if the property doesn’t exist", () => { + const obj = Object.freeze({}); + deleteOwnProperty(obj, "failure"); + assert(!("failure" in obj)); + }); + + it("[[Call]] throws if the property can’t be deleted", () => { + const obj = Object.seal({ failure: undefined }); + assertThrows(() => deleteOwnProperty(obj, "failure")); + }); + + it("[[Call]] returns the provided object", () => { + 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"); }); + }); +}); - it("[[Get]] returns true for a fully‐populated accessor descriptor", () => { - assertStrictEquals( - Reflect.get(PropertyDescriptor.prototype, "isFullyPopulated", { - configurable: true, - enumerable: true, - get: undefined, - set: undefined, - }), - true, - ); +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", () => { + it("[[Call]] returns a frozen object", () => { + assert( + Object.isFrozen( + frozenCopy(Object.create(null), { + data: { + configurable: true, + enumerable: true, + value: undefined, + writable: true, + }, + accessor: { + configurable: true, + enumerable: true, + get: undefined, + }, + }), + ), + ); + }); + + it("[[Call]] ignores non·enumerable properties", () => { + assertEquals( + frozenCopy( + Object.create(null, { + data: { value: undefined }, + accessor: { get: undefined }, + }), + ), + {}, + ); + }); + + it("[[Call]] preserves accessor properties", () => { + const properties = { + both: { + configurable: false, + enumerable: true, + get: () => {}, + set: (_) => {}, + }, + empty: { + configurable: false, + enumerable: true, + get: undefined, + set: undefined, + }, + getter: { + configurable: false, + enumerable: true, + get: () => {}, + set: undefined, + }, + setter: { + configurable: false, + enumerable: true, + get: undefined, + set: (_) => {}, + }, + }; + assertEquals( + Object.getOwnPropertyDescriptors( + frozenCopy(Object.create(null, properties)), + ), + properties, + ); + }); + + it("[[Call]] preserves data properties", () => { + const properties = { + implied: { + configurable: false, + enumerable: true, + }, + writable: { + configurable: false, + enumerable: true, + value: "etaoin", + writable: true, + }, + nonwritable: { + configurable: false, + enumerable: true, + value: "shrdlu", + writable: false, + }, + }; + assertEquals( + Object.getOwnPropertyDescriptors( + frozenCopy(Object.create(null, properties)), + ), + { + implied: { + ...properties.implied, + value: undefined, + writable: false, + }, + writable: { ...properties.writable, writable: false }, + nonwritable: properties.nonwritable, + }, + ); + }); + + it("[[Call]] does not copy properties on the prototype", () => { + assert( + !("failure" in + frozenCopy(Object.create({ failure: undefined }))), + ); + }); + + it("[[Call]] uses the species of the constructor", () => { + const species = { prototype: {} }; + assertStrictEquals( + Object.getPrototypeOf( + frozenCopy({}, { [Symbol.species]: species }), + ), + species.prototype, + ); + }); + + it("[[Call]] uses constructor if no species is defined", () => { + const constructor = { [Symbol.species]: null, prototype: {} }; + assertStrictEquals( + Object.getPrototypeOf(frozenCopy({}, constructor)), + constructor.prototype, + ); + }); + + it("[[Call]] uses the constructor on the object if none is provided", () => { + const constructor = { [Symbol.species]: null, prototype: {} }; + assertStrictEquals( + Object.getPrototypeOf(frozenCopy({ constructor })), + constructor.prototype, + ); + }); + + it("[[Call]] allows a null constructor", () => { + assertStrictEquals( + Object.getPrototypeOf(frozenCopy({}, null)), + 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", () => { + it("[[Call]] gets a method", () => { + const method = () => {}; + assertStrictEquals(getMethod({ method }, "method"), method); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertEquals(getMethod("", "toString"), String.prototype.toString); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => getMethod(null, "valueOf")); + assertThrows(() => getMethod(undefined, "valueOf")); + }); + + 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("getOwnPropertyEntries", () => { + it("[[Call]] gets own (but not inherited) property entries", () => { + assertEquals( + getOwnPropertyEntries({ success: true }), + [["success", true]], + ); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertEquals( + getOwnPropertyEntries("foo"), + [["0", "f"], ["1", "o"], ["2", "o"], ["length", 3]], + ); + }); + + it("[[Call]] uses the provided receiver", () => { + const target = {}; + assertEquals( + getOwnPropertyEntries({ + get success() { + return this; + }, + }, target), + [["success", target]], + ); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => getOwnPropertyEntries(null)); + assertThrows(() => getOwnPropertyEntries(undefined)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new getOwnPropertyEntries({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getOwnPropertyEntries.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + getOwnPropertyEntries.name, + "getOwnPropertyEntries", + ); + }); + }); +}); + +describe("getOwnPropertyKeys", () => { + it("[[Call]] gets own (but not inherited) property keys", () => { + assertEquals(getOwnPropertyKeys({ success: true }), ["success"]); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertEquals(getOwnPropertyKeys("foo"), ["0", "1", "2", "length"]); + }); + + it("[[Call]] throws for null and undefined", () => { + 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("getOwnPropertyValue", () => { + it("[[Call]] gets the own property value", () => { + assertStrictEquals( + getOwnPropertyValue({ success: true }, "success"), + true, + ); + }); + + it("[[Call]] returns undefined for non‐own properties", () => { + assertStrictEquals( + getOwnPropertyValue(Object.create({ success: true }), "success"), + undefined, + ); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertStrictEquals(getOwnPropertyValue("foo", "length"), 3); + }); + + it("[[Call]] uses the provided receiver", () => { + const target = {}; + assertStrictEquals( + getOwnPropertyValue( + { + get success() { + return this; + }, + }, + "success", + target, + ), + target, + ); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => getOwnPropertyValue(null)); + assertThrows(() => getOwnPropertyValue(undefined)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new getOwnPropertyValue({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getOwnPropertyValue.length, 2); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + getOwnPropertyValue.name, + "getOwnPropertyValue", + ); + }); + }); +}); + +describe("getOwnPropertyValues", () => { + it("[[Call]] gets own (but not inherited) property values", () => { + assertEquals(getOwnPropertyValues({ success: true }), [true]); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertEquals( + getOwnPropertyValues("foo"), + ["f", "o", "o", 3], + ); + }); + + it("[[Call]] uses the provided receiver", () => { + const target = {}; + assertEquals( + getOwnPropertyValues({ + get success() { + return this; + }, + }, target), + [target], + ); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => getOwnPropertyValues(null)); + assertThrows(() => getOwnPropertyValues(undefined)); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new getOwnPropertyValues({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(getOwnPropertyValues.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + getOwnPropertyValues.name, + "getOwnPropertyValues", + ); + }); + }); +}); + +describe("getPropertyValue", () => { + it("[[Call]] gets property values on the provided object", () => { + assertStrictEquals( + getPropertyValue({ success: true }, "success"), + true, + ); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertStrictEquals( + getPropertyValue("", "toString"), + String.prototype.toString, + ); + }); + + it("[[Call]] throws for null and undefined", () => { + 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", () => { + it("[[Call]] gets whether a property exists on the provided object", () => { + assertStrictEquals( + 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); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => hasProperty(null, "valueOf")); + assertThrows(() => hasProperty(undefined, "valueOf")); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new hasProperty({}, "valueOf")); + }); + + 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("isArraylikeObject", () => { + it("[[Call]] returns false for primitives", () => { + assertStrictEquals(isArraylikeObject("failure"), false); + }); + + it("[[Call]] returns false if length throws", () => { + assertStrictEquals( + isArraylikeObject({ + get length() { + throw void {}; + }, + }), + false, + ); + }); + + it("[[Call]] returns false if length is not a number and cannot be converted to one", () => { + assertStrictEquals(isArraylikeObject({ length: 1n }), false); + }); + + it("[[Call]] returns true if length is convertable to a number", () => { + assertStrictEquals(isArraylikeObject({ length: -0 }), true); + assertStrictEquals(isArraylikeObject({ length: 1 }), true); + assertStrictEquals(isArraylikeObject({ length: -1.25 }), true); + assertStrictEquals( + isArraylikeObject({ length: 9007199254740992 }), + true, + ); + assertStrictEquals(isArraylikeObject({ length: Infinity }), true); + assertStrictEquals(isArraylikeObject({ length: "success" }), true); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new isArraylikeObject({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(isArraylikeObject.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + isArraylikeObject.name, + "isArraylikeObject", + ); + }); + }); +}); + +describe("isConcatSpreadableObject", () => { + it("[[Call]] returns false for primitives", () => { + assertStrictEquals(isConcatSpreadableObject("failure"), false); + }); + + it("[[Call]] returns false if [Symbol.isConcatSpreadable] is null or false", () => { + assertStrictEquals( + isConcatSpreadableObject( + Object.assign([], { [Symbol.isConcatSpreadable]: null }), + ), + false, + ); + assertStrictEquals( + isConcatSpreadableObject( + Object.assign([], { [Symbol.isConcatSpreadable]: false }), + ), + false, + ); + }); + + it("[[Call]] returns true if [Symbol.isConcatSpreadable] is undefined and the object is an array", () => { + assertStrictEquals( + isConcatSpreadableObject( + Object.assign([], { [Symbol.isConcatSpreadable]: undefined }), + ), + true, + ); + }); + + it("[[Call]] returns true if [Symbol.isConcatSpreadable] is true", () => { + assertStrictEquals( + isConcatSpreadableObject({ [Symbol.isConcatSpreadable]: true }), + true, + ); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new isConcatSpreadableObject({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(isConcatSpreadableObject.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + isConcatSpreadableObject.name, + "isConcatSpreadableObject", + ); + }); + }); +}); + +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("isPropertyDescriptorRecord", () => { + it("[[Call]] returns true for objects created by toPropertyDescriptorRecord", () => { + assertStrictEquals( + isPropertyDescriptorRecord(toPropertyDescriptorRecord({})), + true, + ); + }); + + it("[[Get]] returns false for other objects", () => { + assertStrictEquals( + isPropertyDescriptorRecord(Object.create(null)), + false, + ); + }); + + it("[[Get]] returns false for undefined", () => { + assertStrictEquals(isPropertyDescriptorRecord(undefined), false); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new isPropertyDescriptorRecord({})); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(isPropertyDescriptorRecord.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + isPropertyDescriptorRecord.name, + "isPropertyDescriptorRecord", + ); + }); + }); +}); + +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("lengthOfArraylike", () => { + it("[[Call]] returns the length", () => { + assertStrictEquals( + lengthOfArraylike({ length: 9007199254740991 }), + 9007199254740991, + ); + }); + + it("[[Call]] returns a non·nan result", () => { + assertStrictEquals(lengthOfArraylike({ length: NaN }), 0); + assertStrictEquals(lengthOfArraylike({ length: "failure" }), 0); + }); + + it("[[Call]] returns an integral result", () => { + assertStrictEquals(lengthOfArraylike({ length: 0.25 }), 0); + assertStrictEquals(lengthOfArraylike({ length: 1.1 }), 1); + }); + + it("[[Call]] returns a result greater than or equal to zero", () => { + assertStrictEquals(lengthOfArraylike({ length: -0 }), 0); + assertStrictEquals(lengthOfArraylike({ length: -1 }), 0); + assertStrictEquals(lengthOfArraylike({ length: -Infinity }), 0); + }); + + it("[[Call]] returns a result less than 2 ** 53", () => { + assertStrictEquals( + lengthOfArraylike({ length: 9007199254740992 }), + 9007199254740991, + ); + assertStrictEquals( + lengthOfArraylike({ length: Infinity }), + 9007199254740991, + ); + }); + + it("[[Call]] does not require an object argument", () => { + assertStrictEquals(lengthOfArraylike("string"), 6); + assertStrictEquals(lengthOfArraylike(Symbol()), 0); + }); + + it("[[Construct]] throws an error", () => { + assertThrows(() => new lengthOfArraylike("")); + }); + + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(lengthOfArraylike.length, 1); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals(lengthOfArraylike.name, "lengthOfArraylike"); + }); + }); +}); + +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, + }); + }); + + it("[[Call]] walks the prototype chain", () => { + const setter = spy((_) => {}); + const obj = Object.create( + Object.create(null, { success: { set: setter } }), + ); + setPropertyValue(obj, "success", true); + assertSpyCalls(setter, 1); + assertSpyCall(setter, 0, { + args: [true], + self: obj, + }); + }); + + it("[[Call]] uses the provided receiver", () => { + const setter = spy((_) => {}); + const obj = Object.create(null, { success: { set: setter } }); + const receiver = {}; + setPropertyValue(obj, "success", true, receiver); + assertSpyCalls(setter, 1); + assertSpyCall(setter, 0, { + args: [true], + self: receiver, + }); + }); + + it("[[Call]] throws if the property can’t be set", () => { + const obj = Object.freeze({ failure: undefined }); + assertThrows(() => setPropertyValue(obj, "failure", true)); + }); + + it("[[Call]] returns the provided object", () => { + 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", () => { + it("returns the input for objects", () => { + const obj = {}; + assertStrictEquals(toObject(obj), obj); + }); + + it("throws for nullish values", () => { + assertThrows(() => toObject(null)); + assertThrows(() => toObject(void {})); + }); + + it("returns a wrapper object for other primitives", () => { + const sym = Symbol(); + 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("toPropertyDescriptorRecord", () => { + it("[[Call]] creates a new property descriptor record", () => { + const obj = {}; + const desc = toPropertyDescriptorRecord(obj); + assertEquals(obj, desc); + assertNotStrictEquals(obj, desc); + }); + + it("[[Call]] coerces the values", () => { + assertEquals( + toPropertyDescriptorRecord({ + configurable: undefined, + enumerable: undefined, + writable: undefined, + }), + { configurable: false, enumerable: false, writable: false }, + ); + }); + + it("[[Construct]] throws for primitives", () => { + assertThrows(() => toPropertyDescriptorRecord(undefined)); + assertThrows(() => toPropertyDescriptorRecord("failure")); + }); - describe("::isGenericDescriptor", () => { - it("[[Get]] returns true for a generic descriptor", () => { - assertStrictEquals( - Reflect.get( - PropertyDescriptor.prototype, - "isGenericDescriptor", - {}, - ), - true, - ); - }); + it("[[Construct]] throws an error", () => { + assertThrows(() => new toPropertyDescriptorRecord({})); + }); - it("[[Get]] returns true for a data descriptor", () => { - assertStrictEquals( - Reflect.get( - PropertyDescriptor.prototype, - "isGenericDescriptor", - { value: undefined }, - ), - false, - ); + describe(".length", () => { + it("[[Get]] returns the correct length", () => { + assertStrictEquals(toPropertyDescriptorRecord.length, 1); }); + }); - it("[[Get]] returns false for an accessor descriptor", () => { + describe(".name", () => { + it("[[Get]] returns the correct name", () => { assertStrictEquals( - Reflect.get( - PropertyDescriptor.prototype, - "isGenericDescriptor", - { get: undefined }, - ), - false, + toPropertyDescriptorRecord.name, + "toPropertyDescriptorRecord", ); }); }); describe("~configurable", () => { it("[[DefineOwnProperty]] coerces to a boolean", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); Object.defineProperty(desc, "configurable", {}); assertStrictEquals(desc.configurable, false); }); it("[[DefineOwnProperty]] throws for accessor properties", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); assertThrows(() => Object.defineProperty(desc, "configurable", { get: undefined }) ); }); it("[[Set]] coerces to a boolean", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); desc.configurable = undefined; assertStrictEquals(desc.configurable, false); }); it("[[Delete]] works", () => { - const desc = new PropertyDescriptor({ configurable: false }); + const desc = toPropertyDescriptorRecord({ configurable: false }); delete desc.configurable; assert(!("configurable" in desc)); }); @@ -268,26 +2167,26 @@ describe("PropertyDescriptor", () => { describe("~enumerable", () => { it("[[DefineOwnProperty]] coerces to a boolean", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); Object.defineProperty(desc, "enumerable", {}); assertStrictEquals(desc.enumerable, false); }); it("[[DefineOwnProperty]] throws for accessor properties", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); assertThrows(() => Object.defineProperty(desc, "enumerable", { get: undefined }) ); }); it("[[Set]] coerces to a boolean", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); desc.enumerable = undefined; assertStrictEquals(desc.enumerable, false); }); it("[[Delete]] works", () => { - const desc = new PropertyDescriptor({ enumerable: false }); + const desc = toPropertyDescriptorRecord({ enumerable: false }); delete desc.enumerable; assert(!("enumerable" in desc)); }); @@ -295,49 +2194,49 @@ describe("PropertyDescriptor", () => { describe("~get", () => { it("[[DefineOwnProperty]] works", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); Object.defineProperty(desc, "get", {}); assertStrictEquals(desc.get, undefined); }); it("[[DefineOwnProperty]] throws for accessor properties", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); assertThrows(() => Object.defineProperty(desc, "get", { get: undefined }) ); }); it("[[DefineOwnProperty]] throws if not callable or undefined", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); assertThrows( () => Object.defineProperty(desc, "get", { value: null }), ); }); it("[[DefineOwnProperty]] throws if a data property is defined", () => { - const desc = new PropertyDescriptor({ value: undefined }); + const desc = toPropertyDescriptorRecord({ value: undefined }); assertThrows(() => Object.defineProperty(desc, "get", {})); }); it("[[Set]] works", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); const fn = () => {}; desc.get = fn; assertStrictEquals(desc.get, fn); }); it("[[Set]] throws if not callable or undefined", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); assertThrows(() => desc.get = null); }); it("[[Set]] throws if a data property is defined", () => { - const desc = new PropertyDescriptor({ value: undefined }); + const desc = toPropertyDescriptorRecord({ value: undefined }); assertThrows(() => desc.get = undefined); }); it("[[Delete]] works", () => { - const desc = new PropertyDescriptor({ get: undefined }); + const desc = toPropertyDescriptorRecord({ get: undefined }); delete desc.get; assert(!("get" in desc)); }); @@ -345,49 +2244,49 @@ describe("PropertyDescriptor", () => { describe("~set", () => { it("[[DefineOwnProperty]] works", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); Object.defineProperty(desc, "set", {}); assertStrictEquals(desc.set, undefined); }); it("[[DefineOwnProperty]] throws for accessor properties", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); assertThrows(() => Object.defineProperty(desc, "set", { get: undefined }) ); }); it("[[DefineOwnProperty]] throws if not callable or undefined", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); assertThrows( () => Object.defineProperty(desc, "set", { value: null }), ); }); it("[[DefineOwnProperty]] throws if a data property is defined", () => { - const desc = new PropertyDescriptor({ value: undefined }); + const desc = toPropertyDescriptorRecord({ value: undefined }); assertThrows(() => Object.defineProperty(desc, "set", {})); }); it("[[Set]] works", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); const fn = (_) => {}; desc.set = fn; assertStrictEquals(desc.set, fn); }); it("[[Set]] throws if not callable or undefined", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); assertThrows(() => desc.set = null); }); it("[[Set]] throws if a data property is defined", () => { - const desc = new PropertyDescriptor({ value: undefined }); + const desc = toPropertyDescriptorRecord({ value: undefined }); assertThrows(() => desc.set = undefined); }); it("[[Delete]] works", () => { - const desc = new PropertyDescriptor({ set: undefined }); + const desc = toPropertyDescriptorRecord({ set: undefined }); delete desc.set; assert(!("set" in desc)); }); @@ -395,36 +2294,36 @@ describe("PropertyDescriptor", () => { describe("~value", () => { it("[[DefineOwnProperty]] works", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); Object.defineProperty(desc, "value", {}); assertStrictEquals(desc.value, undefined); }); it("[[DefineOwnProperty]] throws for accessor properties", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); assertThrows(() => Object.defineProperty(desc, "value", { get: undefined }) ); }); it("[[DefineOwnProperty]] throws if an accessor property is defined", () => { - const desc = new PropertyDescriptor({ get: undefined }); + const desc = toPropertyDescriptorRecord({ get: undefined }); assertThrows(() => Object.defineProperty(desc, "value", {})); }); it("[[Set]] works", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); desc.value = "success"; assertStrictEquals(desc.value, "success"); }); it("[[Set]] throws if an accessor property is defined", () => { - const desc = new PropertyDescriptor({ get: undefined }); + const desc = toPropertyDescriptorRecord({ get: undefined }); assertThrows(() => desc.value = null); }); it("[[Delete]] works", () => { - const desc = new PropertyDescriptor({ value: undefined }); + const desc = toPropertyDescriptorRecord({ value: undefined }); delete desc.value; assert(!("value" in desc)); }); @@ -432,303 +2331,38 @@ describe("PropertyDescriptor", () => { describe("~writable", () => { it("[[DefineOwnProperty]] coerces to a boolean", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); Object.defineProperty(desc, "writable", {}); assertStrictEquals(desc.writable, false); }); it("[[DefineOwnProperty]] throws for accessor properties", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); assertThrows(() => Object.defineProperty(desc, "writable", { get: undefined }) ); }); it("[[DefineOwnProperty]] throws if an accessor property is defined", () => { - const desc = new PropertyDescriptor({ get: undefined }); + const desc = toPropertyDescriptorRecord({ get: undefined }); assertThrows(() => Object.defineProperty(desc, "writable", {})); }); it("[[Set]] coerces to a boolean", () => { - const desc = new PropertyDescriptor({}); + const desc = toPropertyDescriptorRecord({}); desc.writable = undefined; assertStrictEquals(desc.writable, false); }); it("[[Set]] throws if an accessor property is defined", () => { - const desc = new PropertyDescriptor({ get: undefined }); + const desc = toPropertyDescriptorRecord({ get: undefined }); assertThrows(() => desc.writable = false); }); it("[[Delete]] works", () => { - const desc = new PropertyDescriptor({ writable: false }); + const desc = toPropertyDescriptorRecord({ writable: false }); delete desc.writable; assert(!("writable" in desc)); }); }); }); - -describe("defineOwnProperties", () => { - it("[[Call]] defines properties from the provided objects", () => { - const obj = {}; - defineOwnProperties(obj, { - etaoin: {}, - shrdlu: {}, - }, { cmfwyp: {} }); - assert("etaoin" in obj); - assert("shrdlu" in obj); - assert("cmfwyp" in obj); - }); - - it("[[Call]] overrides earlier declarations with later ones", () => { - const obj = { etaoin: undefined }; - defineOwnProperties(obj, { - etaoin: { value: "failure" }, - }, { - etaoin: { value: "success" }, - }); - assertStrictEquals(obj.etaoin, "success"); - }); - - it("[[Call]] returns the provided object", () => { - const obj = {}; - assertStrictEquals(defineOwnProperties(obj), obj); - }); -}); - -describe("deleteOwnProperty", () => { - it("[[Call]] deletes the provided property on the provided object", () => { - const obj = { failure: undefined }; - deleteOwnProperty(obj, "failure"); - assert(!("failure" in obj)); - }); - - it("[[Call]] does nothing if the property doesn’t exist", () => { - const obj = Object.freeze({}); - deleteOwnProperty(obj, "failure"); - assert(!("failure" in obj)); - }); - - it("[[Call]] throws if the property can’t be deleted", () => { - const obj = Object.seal({ failure: undefined }); - assertThrows(() => deleteOwnProperty(obj, "failure")); - }); - - it("[[Call]] returns the provided object", () => { - const obj = {}; - assertStrictEquals(deleteOwnProperty(obj, ""), obj); - }); -}); - -describe("frozenCopy", () => { - it("[[Call]] returns a frozen object", () => { - assert( - Object.isFrozen( - frozenCopy(Object.create(null), { - data: { - configurable: true, - enumerable: true, - value: undefined, - writable: true, - }, - accessor: { - configurable: true, - enumerable: true, - get: undefined, - }, - }), - ), - ); - }); - - it("[[Call]] ignores non·enumerable properties", () => { - assertEquals( - frozenCopy( - Object.create(null, { - data: { value: undefined }, - accessor: { get: undefined }, - }), - ), - {}, - ); - }); - - it("[[Call]] preserves accessor properties", () => { - const properties = { - both: { - configurable: false, - enumerable: true, - get: () => {}, - set: (_) => {}, - }, - empty: { - configurable: false, - enumerable: true, - get: undefined, - set: undefined, - }, - getter: { - configurable: false, - enumerable: true, - get: () => {}, - set: undefined, - }, - setter: { - configurable: false, - enumerable: true, - get: undefined, - set: (_) => {}, - }, - }; - assertEquals( - Object.getOwnPropertyDescriptors( - frozenCopy(Object.create(null, properties)), - ), - properties, - ); - }); - - it("[[Call]] does not copy properties on the prototype", () => { - assert( - !("failure" in - frozenCopy(Object.create({ failure: undefined }), { - data: { - configurable: true, - value: undefined, - writable: true, - }, - accessor: { configurable: true, get: undefined }, - })), - ); - }); - - it("[[Call]] uses the species of the constructor", () => { - const species = { prototype: {} }; - assertStrictEquals( - Object.getPrototypeOf( - frozenCopy({}, { [Symbol.species]: species }), - ), - species.prototype, - ); - }); - - it("[[Call]] uses constructor if no species is defined", () => { - const constructor = { [Symbol.species]: null, prototype: {} }; - assertStrictEquals( - Object.getPrototypeOf(frozenCopy({}, constructor)), - constructor.prototype, - ); - }); - - it("[[Call]] uses the constructor on the object if none is provided", () => { - const constructor = { [Symbol.species]: null, prototype: {} }; - assertStrictEquals( - Object.getPrototypeOf(frozenCopy({ constructor })), - constructor.prototype, - ); - }); - - it("[[Call]] allows a null constructor", () => { - assertStrictEquals( - Object.getPrototypeOf(frozenCopy({}, null)), - null, - ); - }); -}); - -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, - }); - }); - - it("[[Call]] walks the prototype chain", () => { - const setter = spy((_) => {}); - const obj = Object.create( - Object.create(null, { success: { set: setter } }), - ); - setPropertyValue(obj, "success", true); - assertSpyCalls(setter, 1); - assertSpyCall(setter, 0, { - args: [true], - self: obj, - }); - }); - - it("[[Call]] uses the provided receiver", () => { - const setter = spy((_) => {}); - const obj = Object.create(null, { success: { set: setter } }); - const receiver = {}; - setPropertyValue(obj, "success", true, receiver); - assertSpyCalls(setter, 1); - assertSpyCall(setter, 0, { - args: [true], - self: receiver, - }); - }); - - it("[[Call]] throws if the property can’t be set", () => { - const obj = Object.freeze({ failure: undefined }); - assertThrows(() => setPropertyValue(obj, "failure", true)); - }); - - it("[[Call]] returns the provided object", () => { - const obj = {}; - assertStrictEquals(setPropertyValue(obj, "", undefined), obj); - }); -}); - -describe("toObject", () => { - it("returns the input for objects", () => { - const obj = {}; - assertStrictEquals(toObject(obj), obj); - }); - - it("returns a new object for nullish values", () => { - assertEquals(toObject(null), {}); - assertEquals(toObject(void {}), {}); - }); - - it("returns a wrapper object for other primitives", () => { - const sym = Symbol(); - assertStrictEquals(typeof toObject(sym), "object"); - assertStrictEquals(toObject(sym).valueOf(), sym); - }); -}); - -describe("toPropertyKey", () => { - it("returns a string or symbol", () => { - const sym = Symbol(); - assertStrictEquals(toPropertyKey(sym), sym); - assertStrictEquals( - toPropertyKey(new String("success")), - "success", - ); - }); - - it("favours the `toString` representation", () => { - assertStrictEquals( - toPropertyKey({ - toString() { - return "success"; - }, - valueOf() { - return "failure"; - }, - }), - "success", - ); - }); -});