]> Lady’s Gitweb - Pisces/commitdiff
Add LazyLoader class 0.1.2
authorLady <redacted>
Sat, 23 Jul 2022 23:13:27 +0000 (16:13 -0700)
committerLady <redacted>
Fri, 12 May 2023 03:56:47 +0000 (20:56 -0700)
object.js
object.test.js

index 578f84f5201f9d46907cc77ba36a30d1c984ddcb..a34423e7a5bbd1578154690f7f8d69767fb7e776 100644 (file)
--- a/object.js
+++ b/object.js
 import { bind, call } from "./function.js";
 import { toPrimitive, type } from "./value.js";
 
+/**
+ * An object whose properties are lazy‐loaded from the methods on the
+ * own properties of the provided object.
+ *
+ * This is useful when you are looking to reference properties on
+ * objects which, due to module dependency graphs, cannot be guaranteed
+ * to have been initialized yet.
+ *
+ * The resulting properties will have the same attributes (regarding
+ * configurability, enumerability, and writability) as the
+ * corresponding properties on the methods object. If a property is
+ * marked as writable, the method will never be called if it is set
+ * before it is gotten. By necessity, the resulting properties are all
+ * configurable before they are accessed for the first time.
+ *
+ * Methods will be called with the resulting object as their this
+ * value.
+ *
+ * LazyLoader objects have the same prototype as the passed methods
+ * object.
+ */
+export class LazyLoader extends null {
+  /** Constructs a new LazyLoader object. */
+  constructor(loadMethods) {
+    const result = objectCreate(getPrototype(loadMethods));
+    const methodKeys = getOwnPropertyKeys(loadMethods);
+    for (let index = 0; index < methodKeys.length; ++index) {
+      const methodKey = methodKeys[index];
+      const { configurable, enumerable, writable } =
+        getOwnPropertyDescriptor(loadMethods, methodKey);
+      defineOwnProperty(result, methodKey, {
+        configurable: true,
+        enumerable,
+        get: () => {
+          const value = call(loadMethods[methodKey], result, []);
+          defineOwnProperty(result, methodKey, {
+            configurable,
+            enumerable,
+            value,
+            writable,
+          });
+          return value;
+        },
+        set: writable
+          ? ($) =>
+            defineOwnProperty(result, methodKey, {
+              configurable,
+              enumerable,
+              value: $,
+              writable,
+            })
+          : void {},
+      });
+    }
+    return result;
+  }
+}
+
 /**
  * A property descriptor object.
  *
index da890d38f3aa5cd1f7152706d5b02bd7085a4650..2b6cbb8163a43d749199baecde1fa2022fee98b2 100644 (file)
@@ -22,12 +22,251 @@ import {
   defineOwnProperties,
   deleteOwnProperty,
   frozenCopy,
+  LazyLoader,
   PropertyDescriptor,
   setPropertyValue,
   toObject,
   toPropertyKey,
 } from "./object.js";
 
+describe("LazyLoader", () => {
+  const symbol = Symbol();
+  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("[[Construct]] creates a new object which inherits from the correct prototype", () => {
+    assertStrictEquals(
+      Object.getPrototypeOf(new LazyLoader(methodsObject)),
+      prototype,
+    );
+  });
+
+  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 !== void {},
+            ];
+          }
+        }(new LazyLoader(methodsObject)),
+      ),
+      {
+        etaoin: true,
+        shrdlu: true,
+        cmfwyp: true,
+        vbgkqj: true,
+        xzfiflffffi: true,
+        [symbol]: true,
+      },
+    );
+  });
+
+  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 !== void {},
+            ];
+          }
+        }(new LazyLoader(methodsObject)),
+      ),
+      {
+        etaoin: false,
+        shrdlu: false,
+        cmfwyp: false,
+        vbgkqj: false,
+        xzfiflffffi: false,
+        [symbol]: true,
+      },
+    );
+  });
+
+  describe("[[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: "success",
+        writable: false,
+      },
+    );
+    assertSpyCalls(shrdluMethod, 1);
+    assertSpyCall(shrdluMethod, 0, {
+      args: [],
+      self: ll,
+      returned: "success",
+    });
+    ll.cmfwyp;
+    assertEquals(
+      Object.getOwnPropertyDescriptor(ll, "cmfwyp"),
+      {
+        configurable: true,
+        enumerable: false,
+        value: "success",
+        writable: false,
+      },
+    );
+    assertSpyCalls(cmfwypMethod, 1);
+    assertSpyCall(cmfwypMethod, 0, {
+      args: [],
+      self: ll,
+      returned: "success",
+    });
+    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]);
+  });
+
+  describe("[[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,
+        value: "success",
+        writable: true,
+      },
+    );
+  });
+});
+
 describe("PropertyDescriptor", () => {
   it("[[Construct]] creates a new PropertyDescriptor", () => {
     assertStrictEquals(
This page took 0.036075 seconds and 4 git commands to generate.