X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/50ab30fc3a257e46da6b8bdc0dad054f729e16e1..HEAD:/object.test.js diff --git a/object.test.js b/object.test.js index 38d333f..cba44b9 100644 --- a/object.test.js +++ b/object.test.js @@ -10,6 +10,7 @@ import { assert, assertEquals, + assertNotStrictEquals, assertSpyCall, assertSpyCalls, assertStrictEquals, @@ -19,6 +20,8 @@ import { spy, } from "./dev-deps.js"; import { + defineOwnDataProperty, + defineOwnNonenumerableDataProperty, defineOwnProperties, defineOwnProperty, deleteOwnProperty, @@ -27,9 +30,12 @@ import { getMethod, getOwnPropertyDescriptor, getOwnPropertyDescriptors, + getOwnPropertyEntries, getOwnPropertyKeys, getOwnPropertyStrings, getOwnPropertySymbols, + getOwnPropertyValue, + getOwnPropertyValues, getPropertyValue, getPrototype, hasOwnProperty, @@ -37,6 +43,7 @@ import { isArraylikeObject, isConcatSpreadableObject, isExtensibleObject, + isPropertyDescriptorRecord, isUnfrozenObject, isUnsealedObject, LazyLoader, @@ -52,7 +59,7 @@ import { setPropertyValues, setPrototype, toObject, - toPropertyKey, + toPropertyDescriptorRecord, } from "./object.js"; describe("LazyLoader", () => { @@ -309,11 +316,113 @@ describe("LazyLoader", () => { }); }); +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); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + defineOwnDataProperty.name, + "defineOwnDataProperty", + ); + }); + }); +}); + +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); + }); + }); + + describe(".name", () => { + it("[[Get]] returns the correct name", () => { + assertStrictEquals( + defineOwnNonenumerableDataProperty.name, + "defineOwnNonenumerableDataProperty", + ); + }); + }); +}); + describe("defineOwnProperty", () => { it("[[Call]] defines the property", () => { const obj = {}; defineOwnProperty(obj, "etaoin", {}); - assert("etaoin" in obj); + assert(Object.hasOwn(obj, "etaoin")); }); it("[[Call]] returns the provided object", () => { @@ -525,17 +634,45 @@ describe("frozenCopy", () => { ); }); + 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 }), { - data: { - configurable: true, - value: undefined, - writable: true, - }, - accessor: { configurable: true, get: undefined }, - })), + frozenCopy(Object.create({ failure: undefined }))), ); }); @@ -706,6 +843,58 @@ describe("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"]); @@ -816,6 +1005,115 @@ describe("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( @@ -1113,6 +1411,45 @@ describe("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); @@ -1758,43 +2095,274 @@ describe("toObject", () => { }); }); -describe("toPropertyKey", () => { - it("returns a string or symbol", () => { - const sym = Symbol(); - assertStrictEquals(toPropertyKey(sym), sym); - assertStrictEquals( - toPropertyKey(new String("success")), - "success", - ); +describe("toPropertyDescriptorRecord", () => { + it("[[Call]] creates a new property descriptor record", () => { + const obj = {}; + const desc = toPropertyDescriptorRecord(obj); + assertEquals(obj, desc); + assertNotStrictEquals(obj, desc); }); - it("favours the `toString` representation", () => { - assertStrictEquals( - toPropertyKey({ - toString() { - return "success"; - }, - valueOf() { - return "failure"; - }, + it("[[Call]] coerces the values", () => { + assertEquals( + toPropertyDescriptorRecord({ + configurable: undefined, + enumerable: undefined, + writable: undefined, }), - "success", + { configurable: false, enumerable: false, writable: false }, ); }); + it("[[Construct]] throws for primitives", () => { + assertThrows(() => toPropertyDescriptorRecord(undefined)); + assertThrows(() => toPropertyDescriptorRecord("failure")); + }); + it("[[Construct]] throws an error", () => { - assertThrows(() => new toPropertyKey("")); + assertThrows(() => new toPropertyDescriptorRecord({})); }); describe(".length", () => { it("[[Get]] returns the correct length", () => { - assertStrictEquals(toPropertyKey.length, 1); + assertStrictEquals(toPropertyDescriptorRecord.length, 1); }); }); describe(".name", () => { it("[[Get]] returns the correct name", () => { - assertStrictEquals(toPropertyKey.name, "toPropertyKey"); + assertStrictEquals( + toPropertyDescriptorRecord.name, + "toPropertyDescriptorRecord", + ); + }); + }); + + describe("~configurable", () => { + it("[[DefineOwnProperty]] coerces to a boolean", () => { + const desc = toPropertyDescriptorRecord({}); + Object.defineProperty(desc, "configurable", {}); + assertStrictEquals(desc.configurable, false); + }); + + it("[[DefineOwnProperty]] throws for accessor properties", () => { + const desc = toPropertyDescriptorRecord({}); + assertThrows(() => + Object.defineProperty(desc, "configurable", { get: undefined }) + ); + }); + + it("[[Set]] coerces to a boolean", () => { + const desc = toPropertyDescriptorRecord({}); + desc.configurable = undefined; + assertStrictEquals(desc.configurable, false); + }); + + it("[[Delete]] works", () => { + const desc = toPropertyDescriptorRecord({ configurable: false }); + delete desc.configurable; + assert(!("configurable" in desc)); + }); + }); + + describe("~enumerable", () => { + it("[[DefineOwnProperty]] coerces to a boolean", () => { + const desc = toPropertyDescriptorRecord({}); + Object.defineProperty(desc, "enumerable", {}); + assertStrictEquals(desc.enumerable, false); + }); + + it("[[DefineOwnProperty]] throws for accessor properties", () => { + const desc = toPropertyDescriptorRecord({}); + assertThrows(() => + Object.defineProperty(desc, "enumerable", { get: undefined }) + ); + }); + + it("[[Set]] coerces to a boolean", () => { + const desc = toPropertyDescriptorRecord({}); + desc.enumerable = undefined; + assertStrictEquals(desc.enumerable, false); + }); + + it("[[Delete]] works", () => { + const desc = toPropertyDescriptorRecord({ enumerable: false }); + delete desc.enumerable; + assert(!("enumerable" in desc)); + }); + }); + + describe("~get", () => { + it("[[DefineOwnProperty]] works", () => { + const desc = toPropertyDescriptorRecord({}); + Object.defineProperty(desc, "get", {}); + assertStrictEquals(desc.get, undefined); + }); + + it("[[DefineOwnProperty]] throws for accessor properties", () => { + const desc = toPropertyDescriptorRecord({}); + assertThrows(() => + Object.defineProperty(desc, "get", { get: undefined }) + ); + }); + + it("[[DefineOwnProperty]] throws if not callable or undefined", () => { + const desc = toPropertyDescriptorRecord({}); + assertThrows( + () => Object.defineProperty(desc, "get", { value: null }), + ); + }); + + it("[[DefineOwnProperty]] throws if a data property is defined", () => { + const desc = toPropertyDescriptorRecord({ value: undefined }); + assertThrows(() => Object.defineProperty(desc, "get", {})); + }); + + it("[[Set]] works", () => { + const desc = toPropertyDescriptorRecord({}); + const fn = () => {}; + desc.get = fn; + assertStrictEquals(desc.get, fn); + }); + + it("[[Set]] throws if not callable or undefined", () => { + const desc = toPropertyDescriptorRecord({}); + assertThrows(() => desc.get = null); + }); + + it("[[Set]] throws if a data property is defined", () => { + const desc = toPropertyDescriptorRecord({ value: undefined }); + assertThrows(() => desc.get = undefined); + }); + + it("[[Delete]] works", () => { + const desc = toPropertyDescriptorRecord({ get: undefined }); + delete desc.get; + assert(!("get" in desc)); + }); + }); + + describe("~set", () => { + it("[[DefineOwnProperty]] works", () => { + const desc = toPropertyDescriptorRecord({}); + Object.defineProperty(desc, "set", {}); + assertStrictEquals(desc.set, undefined); + }); + + it("[[DefineOwnProperty]] throws for accessor properties", () => { + const desc = toPropertyDescriptorRecord({}); + assertThrows(() => + Object.defineProperty(desc, "set", { get: undefined }) + ); + }); + + it("[[DefineOwnProperty]] throws if not callable or undefined", () => { + const desc = toPropertyDescriptorRecord({}); + assertThrows( + () => Object.defineProperty(desc, "set", { value: null }), + ); + }); + + it("[[DefineOwnProperty]] throws if a data property is defined", () => { + const desc = toPropertyDescriptorRecord({ value: undefined }); + assertThrows(() => Object.defineProperty(desc, "set", {})); + }); + + it("[[Set]] works", () => { + const desc = toPropertyDescriptorRecord({}); + const fn = (_) => {}; + desc.set = fn; + assertStrictEquals(desc.set, fn); + }); + + it("[[Set]] throws if not callable or undefined", () => { + const desc = toPropertyDescriptorRecord({}); + assertThrows(() => desc.set = null); + }); + + it("[[Set]] throws if a data property is defined", () => { + const desc = toPropertyDescriptorRecord({ value: undefined }); + assertThrows(() => desc.set = undefined); + }); + + it("[[Delete]] works", () => { + const desc = toPropertyDescriptorRecord({ set: undefined }); + delete desc.set; + assert(!("set" in desc)); + }); + }); + + describe("~value", () => { + it("[[DefineOwnProperty]] works", () => { + const desc = toPropertyDescriptorRecord({}); + Object.defineProperty(desc, "value", {}); + assertStrictEquals(desc.value, undefined); + }); + + it("[[DefineOwnProperty]] throws for accessor properties", () => { + const desc = toPropertyDescriptorRecord({}); + assertThrows(() => + Object.defineProperty(desc, "value", { get: undefined }) + ); + }); + + it("[[DefineOwnProperty]] throws if an accessor property is defined", () => { + const desc = toPropertyDescriptorRecord({ get: undefined }); + assertThrows(() => Object.defineProperty(desc, "value", {})); + }); + + it("[[Set]] works", () => { + const desc = toPropertyDescriptorRecord({}); + desc.value = "success"; + assertStrictEquals(desc.value, "success"); + }); + + it("[[Set]] throws if an accessor property is defined", () => { + const desc = toPropertyDescriptorRecord({ get: undefined }); + assertThrows(() => desc.value = null); + }); + + it("[[Delete]] works", () => { + const desc = toPropertyDescriptorRecord({ value: undefined }); + delete desc.value; + assert(!("value" in desc)); + }); + }); + + describe("~writable", () => { + it("[[DefineOwnProperty]] coerces to a boolean", () => { + const desc = toPropertyDescriptorRecord({}); + Object.defineProperty(desc, "writable", {}); + assertStrictEquals(desc.writable, false); + }); + + it("[[DefineOwnProperty]] throws for accessor properties", () => { + const desc = toPropertyDescriptorRecord({}); + assertThrows(() => + Object.defineProperty(desc, "writable", { get: undefined }) + ); + }); + + it("[[DefineOwnProperty]] throws if an accessor property is defined", () => { + const desc = toPropertyDescriptorRecord({ get: undefined }); + assertThrows(() => Object.defineProperty(desc, "writable", {})); + }); + + it("[[Set]] coerces to a boolean", () => { + const desc = toPropertyDescriptorRecord({}); + desc.writable = undefined; + assertStrictEquals(desc.writable, false); + }); + + it("[[Set]] throws if an accessor property is defined", () => { + const desc = toPropertyDescriptorRecord({ get: undefined }); + assertThrows(() => desc.writable = false); + }); + + it("[[Delete]] works", () => { + const desc = toPropertyDescriptorRecord({ writable: false }); + delete desc.writable; + assert(!("writable" in desc)); }); }); });