]> Lady’s Gitweb - Pisces/blobdiff - object.test.js
Split some object code into value and unit test
[Pisces] / object.test.js
diff --git a/object.test.js b/object.test.js
new file mode 100644 (file)
index 0000000..da890d3
--- /dev/null
@@ -0,0 +1,734 @@
+// ♓🌟 Piscēs ∷ object.test.js
+// ====================================================================
+//
+// Copyright © 2022 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
+// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
+
+import {
+  assert,
+  assertEquals,
+  assertSpyCall,
+  assertSpyCalls,
+  assertStrictEquals,
+  assertThrows,
+  describe,
+  it,
+  spy,
+} from "./dev-deps.js";
+import {
+  defineOwnProperties,
+  deleteOwnProperty,
+  frozenCopy,
+  PropertyDescriptor,
+  setPropertyValue,
+  toObject,
+  toPropertyKey,
+} from "./object.js";
+
+describe("PropertyDescriptor", () => {
+  it("[[Construct]] creates a new PropertyDescriptor", () => {
+    assertStrictEquals(
+      Object.getPrototypeOf(new PropertyDescriptor({})),
+      PropertyDescriptor.prototype,
+    );
+  });
+
+  it("[[Construct]] throws for primitives", () => {
+    assertThrows(() => new PropertyDescriptor("failure"));
+  });
+
+  describe("::complete", () => {
+    it("[[Call]] completes a generic descriptor", () => {
+      const desc = {};
+      PropertyDescriptor.prototype.complete.call(desc);
+      assertEquals(desc, {
+        configurable: false,
+        enumerable: false,
+        value: undefined,
+        writable: false,
+      });
+    });
+
+    it("[[Call]] completes a data descriptor", () => {
+      const desc = { value: undefined };
+      PropertyDescriptor.prototype.complete.call(desc);
+      assertEquals(desc, {
+        configurable: false,
+        enumerable: false,
+        value: undefined,
+        writable: false,
+      });
+    });
+
+    it("[[Call]] completes an accessor descriptor", () => {
+      const desc = { get: undefined };
+      PropertyDescriptor.prototype.complete.call(desc);
+      assertEquals(desc, {
+        configurable: false,
+        enumerable: false,
+        get: undefined,
+        set: undefined,
+      });
+    });
+  });
+
+  describe("::isAccessorDescriptor", () => {
+    it("[[Get]] returns false for a generic descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(
+          PropertyDescriptor.prototype,
+          "isAccessorDescriptor",
+          {},
+        ),
+        false,
+      );
+    });
+
+    it("[[Get]] returns false for a data descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(
+          PropertyDescriptor.prototype,
+          "isAccessorDescriptor",
+          { value: undefined },
+        ),
+        false,
+      );
+    });
+
+    it("[[Get]] returns true for an accessor descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(
+          PropertyDescriptor.prototype,
+          "isAccessorDescriptor",
+          { get: undefined },
+        ),
+        true,
+      );
+    });
+  });
+
+  describe("::isDataDescriptor", () => {
+    it("[[Get]] returns false for a generic descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(
+          PropertyDescriptor.prototype,
+          "isDataDescriptor",
+          {},
+        ),
+        false,
+      );
+    });
+
+    it("[[Get]] returns true for a data descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(
+          PropertyDescriptor.prototype,
+          "isDataDescriptor",
+          { value: undefined },
+        ),
+        true,
+      );
+    });
+
+    it("[[Get]] returns false for an accessor descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(
+          PropertyDescriptor.prototype,
+          "isDataDescriptor",
+          { get: undefined },
+        ),
+        false,
+      );
+    });
+  });
+
+  describe("::isFullyPopulated", () => {
+    it("[[Get]] returns false for a generic descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(
+          PropertyDescriptor.prototype,
+          "isFullyPopulated",
+          {},
+        ),
+        false,
+      );
+    });
+
+    it("[[Get]] returns false for a non‐fully‐populated data descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(
+          PropertyDescriptor.prototype,
+          "isFullyPopulated",
+          { value: undefined },
+        ),
+        false,
+      );
+    });
+
+    it("[[Get]] returns true for a fully‐populated data descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(PropertyDescriptor.prototype, "isFullyPopulated", {
+          configurable: true,
+          enumerable: true,
+          value: undefined,
+          writable: true,
+        }),
+        true,
+      );
+    });
+
+    it("[[Get]] returns false for a non‐fully‐populated accessor descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(
+          PropertyDescriptor.prototype,
+          "isFullyPopulated",
+          { get: undefined },
+        ),
+        false,
+      );
+    });
+
+    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("::isGenericDescriptor", () => {
+    it("[[Get]] returns true for a generic descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(
+          PropertyDescriptor.prototype,
+          "isGenericDescriptor",
+          {},
+        ),
+        true,
+      );
+    });
+
+    it("[[Get]] returns true for a data descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(
+          PropertyDescriptor.prototype,
+          "isGenericDescriptor",
+          { value: undefined },
+        ),
+        false,
+      );
+    });
+
+    it("[[Get]] returns false for an accessor descriptor", () => {
+      assertStrictEquals(
+        Reflect.get(
+          PropertyDescriptor.prototype,
+          "isGenericDescriptor",
+          { get: undefined },
+        ),
+        false,
+      );
+    });
+  });
+
+  describe("~configurable", () => {
+    it("[[DefineOwnProperty]] coerces to a boolean", () => {
+      const desc = new PropertyDescriptor({});
+      Object.defineProperty(desc, "configurable", {});
+      assertStrictEquals(desc.configurable, false);
+    });
+
+    it("[[DefineOwnProperty]] throws for accessor properties", () => {
+      const desc = new PropertyDescriptor({});
+      assertThrows(() =>
+        Object.defineProperty(desc, "configurable", { get: undefined })
+      );
+    });
+
+    it("[[Set]] coerces to a boolean", () => {
+      const desc = new PropertyDescriptor({});
+      desc.configurable = undefined;
+      assertStrictEquals(desc.configurable, false);
+    });
+
+    it("[[Delete]] works", () => {
+      const desc = new PropertyDescriptor({ configurable: false });
+      delete desc.configurable;
+      assert(!("configurable" in desc));
+    });
+  });
+
+  describe("~enumerable", () => {
+    it("[[DefineOwnProperty]] coerces to a boolean", () => {
+      const desc = new PropertyDescriptor({});
+      Object.defineProperty(desc, "enumerable", {});
+      assertStrictEquals(desc.enumerable, false);
+    });
+
+    it("[[DefineOwnProperty]] throws for accessor properties", () => {
+      const desc = new PropertyDescriptor({});
+      assertThrows(() =>
+        Object.defineProperty(desc, "enumerable", { get: undefined })
+      );
+    });
+
+    it("[[Set]] coerces to a boolean", () => {
+      const desc = new PropertyDescriptor({});
+      desc.enumerable = undefined;
+      assertStrictEquals(desc.enumerable, false);
+    });
+
+    it("[[Delete]] works", () => {
+      const desc = new PropertyDescriptor({ enumerable: false });
+      delete desc.enumerable;
+      assert(!("enumerable" in desc));
+    });
+  });
+
+  describe("~get", () => {
+    it("[[DefineOwnProperty]] works", () => {
+      const desc = new PropertyDescriptor({});
+      Object.defineProperty(desc, "get", {});
+      assertStrictEquals(desc.get, undefined);
+    });
+
+    it("[[DefineOwnProperty]] throws for accessor properties", () => {
+      const desc = new PropertyDescriptor({});
+      assertThrows(() =>
+        Object.defineProperty(desc, "get", { get: undefined })
+      );
+    });
+
+    it("[[DefineOwnProperty]] throws if not callable or undefined", () => {
+      const desc = new PropertyDescriptor({});
+      assertThrows(
+        () => Object.defineProperty(desc, "get", { value: null }),
+      );
+    });
+
+    it("[[DefineOwnProperty]] throws if a data property is defined", () => {
+      const desc = new PropertyDescriptor({ value: undefined });
+      assertThrows(() => Object.defineProperty(desc, "get", {}));
+    });
+
+    it("[[Set]] works", () => {
+      const desc = new PropertyDescriptor({});
+      const fn = () => {};
+      desc.get = fn;
+      assertStrictEquals(desc.get, fn);
+    });
+
+    it("[[Set]] throws if not callable or undefined", () => {
+      const desc = new PropertyDescriptor({});
+      assertThrows(() => desc.get = null);
+    });
+
+    it("[[Set]] throws if a data property is defined", () => {
+      const desc = new PropertyDescriptor({ value: undefined });
+      assertThrows(() => desc.get = undefined);
+    });
+
+    it("[[Delete]] works", () => {
+      const desc = new PropertyDescriptor({ get: undefined });
+      delete desc.get;
+      assert(!("get" in desc));
+    });
+  });
+
+  describe("~set", () => {
+    it("[[DefineOwnProperty]] works", () => {
+      const desc = new PropertyDescriptor({});
+      Object.defineProperty(desc, "set", {});
+      assertStrictEquals(desc.set, undefined);
+    });
+
+    it("[[DefineOwnProperty]] throws for accessor properties", () => {
+      const desc = new PropertyDescriptor({});
+      assertThrows(() =>
+        Object.defineProperty(desc, "set", { get: undefined })
+      );
+    });
+
+    it("[[DefineOwnProperty]] throws if not callable or undefined", () => {
+      const desc = new PropertyDescriptor({});
+      assertThrows(
+        () => Object.defineProperty(desc, "set", { value: null }),
+      );
+    });
+
+    it("[[DefineOwnProperty]] throws if a data property is defined", () => {
+      const desc = new PropertyDescriptor({ value: undefined });
+      assertThrows(() => Object.defineProperty(desc, "set", {}));
+    });
+
+    it("[[Set]] works", () => {
+      const desc = new PropertyDescriptor({});
+      const fn = (_) => {};
+      desc.set = fn;
+      assertStrictEquals(desc.set, fn);
+    });
+
+    it("[[Set]] throws if not callable or undefined", () => {
+      const desc = new PropertyDescriptor({});
+      assertThrows(() => desc.set = null);
+    });
+
+    it("[[Set]] throws if a data property is defined", () => {
+      const desc = new PropertyDescriptor({ value: undefined });
+      assertThrows(() => desc.set = undefined);
+    });
+
+    it("[[Delete]] works", () => {
+      const desc = new PropertyDescriptor({ set: undefined });
+      delete desc.set;
+      assert(!("set" in desc));
+    });
+  });
+
+  describe("~value", () => {
+    it("[[DefineOwnProperty]] works", () => {
+      const desc = new PropertyDescriptor({});
+      Object.defineProperty(desc, "value", {});
+      assertStrictEquals(desc.value, undefined);
+    });
+
+    it("[[DefineOwnProperty]] throws for accessor properties", () => {
+      const desc = new PropertyDescriptor({});
+      assertThrows(() =>
+        Object.defineProperty(desc, "value", { get: undefined })
+      );
+    });
+
+    it("[[DefineOwnProperty]] throws if an accessor property is defined", () => {
+      const desc = new PropertyDescriptor({ get: undefined });
+      assertThrows(() => Object.defineProperty(desc, "value", {}));
+    });
+
+    it("[[Set]] works", () => {
+      const desc = new PropertyDescriptor({});
+      desc.value = "success";
+      assertStrictEquals(desc.value, "success");
+    });
+
+    it("[[Set]] throws if an accessor property is defined", () => {
+      const desc = new PropertyDescriptor({ get: undefined });
+      assertThrows(() => desc.value = null);
+    });
+
+    it("[[Delete]] works", () => {
+      const desc = new PropertyDescriptor({ value: undefined });
+      delete desc.value;
+      assert(!("value" in desc));
+    });
+  });
+
+  describe("~writable", () => {
+    it("[[DefineOwnProperty]] coerces to a boolean", () => {
+      const desc = new PropertyDescriptor({});
+      Object.defineProperty(desc, "writable", {});
+      assertStrictEquals(desc.writable, false);
+    });
+
+    it("[[DefineOwnProperty]] throws for accessor properties", () => {
+      const desc = new PropertyDescriptor({});
+      assertThrows(() =>
+        Object.defineProperty(desc, "writable", { get: undefined })
+      );
+    });
+
+    it("[[DefineOwnProperty]] throws if an accessor property is defined", () => {
+      const desc = new PropertyDescriptor({ get: undefined });
+      assertThrows(() => Object.defineProperty(desc, "writable", {}));
+    });
+
+    it("[[Set]] coerces to a boolean", () => {
+      const desc = new PropertyDescriptor({});
+      desc.writable = undefined;
+      assertStrictEquals(desc.writable, false);
+    });
+
+    it("[[Set]] throws if an accessor property is defined", () => {
+      const desc = new PropertyDescriptor({ get: undefined });
+      assertThrows(() => desc.writable = false);
+    });
+
+    it("[[Delete]] works", () => {
+      const desc = new PropertyDescriptor({ 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",
+    );
+  });
+});
This page took 0.033082 seconds and 4 git commands to generate.