]> Lady’s Gitweb - Etiquette/blobdiff - memory.test.js
Initial {memory ∣ storage} model
[Etiquette] / memory.test.js
diff --git a/memory.test.js b/memory.test.js
new file mode 100644 (file)
index 0000000..8187635
--- /dev/null
@@ -0,0 +1,267 @@
+// 📧🏷️ Étiquette ∷ memory.test.js
+// ====================================================================
+//
+// Copyright © 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
+// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
+
+import {
+  assert,
+  assertEquals,
+  assertStrictEquals,
+  assertThrows,
+  beforeEach,
+  describe,
+  it,
+} from "./dev-deps.js";
+import { Storage } from "./memory.js";
+
+/** A simple storable class for use in testing. */
+class Storable {
+  /** Constructs a new instance with the provided data. */
+  constructor(data) {
+    this.data = data;
+    this.id = null;
+  }
+
+  /** Returns a new instance with the provided data and id. */
+  static [Storage.toInstance](data, id) {
+    const result = new Storable(data);
+    result.id = id;
+    return result;
+  }
+
+  /** Returns the data of this instance. */
+  [Storage.toObject]() {
+    return this.data;
+  }
+}
+
+describe("Storage", () => {
+  it("[[Construct]] creates a new Storage", () => {
+    assertStrictEquals(
+      Object.getPrototypeOf(new Storage()),
+      Storage.prototype,
+    );
+  });
+
+  describe(".toInstance", () => {
+    it("[[Get]] returns a symbol", () => {
+      assertStrictEquals(typeof Storage.toInstance, "symbol");
+    });
+
+    it("[[Set]] throws", () => {
+      assertThrows(() => Storage.toInstance = null);
+    });
+  });
+
+  describe(".toObject", () => {
+    it("[[Get]] returns a symbol", () => {
+      assertStrictEquals(typeof Storage.toObject, "symbol");
+    });
+
+    it("[[Set]] throws", () => {
+      assertThrows(() => Storage.toObject = null);
+    });
+  });
+
+  describe("::add", () => {
+    let instance;
+
+    beforeEach(() => {
+      instance = new Storage();
+    });
+
+    it("[[Call]] returns an id", () => {
+      const result = instance.add(new Storable());
+      assertStrictEquals(typeof result, "string");
+      assert(
+        /^[0-9A-Z*~$=][0-9A-TV-Z]{2}-[0-9A-TV-Z]{4}$/u.test(result),
+      );
+    });
+
+    it("[[Call]] stores data for retrieval later", () => {
+      const data = { my: "data" };
+      const storable = new Storable(data);
+      const newID = instance.add(storable);
+      assertEquals(instance.get(newID).data, data);
+    });
+
+    it("[[Call]] does not store non‐enumerable properties", () => {
+      const data = Object.create(null, {
+        gone: { enumerable: false },
+      });
+      const storable = new Storable(data);
+      const newID = instance.add(storable);
+      assert(!("gone" in instance.get(newID).data));
+    });
+
+    it("[[Call]] does not store prototype properties", () => {
+      const data = Object.create({ gone: true });
+      const storable = new Storable(data);
+      const newID = instance.add(storable);
+      assert(!("gone" in instance.get(newID).data));
+    });
+
+    it("[[Call]] throws if the provided value is not an object", () => {
+      assertThrows(() => {
+        instance.add("");
+      });
+    });
+
+    it("[[Call]] throws if the provided value does not implement `[Storage.toObject]`", () => {
+      assertThrows(() => {
+        instance.add(Object.create(null));
+      });
+    });
+  });
+
+  describe("::delete", () => {
+    let instance;
+
+    beforeEach(() => {
+      instance = new Storage();
+    });
+
+    it("[[Call]] returns whether the value was assigned", () => {
+      assertStrictEquals(instance.delete("000-0000"), false);
+      const newID = instance.add(new Storable());
+      assertStrictEquals(instance.delete(newID), true);
+    });
+
+    it("[[Call]] deletes the value", () => {
+      const newID = instance.add(new Storable());
+      instance.delete(newID);
+      assertStrictEquals(instance.get(newID), null);
+    });
+
+    it("[[Call]] throws if the provided identifier is invalid", () => {
+      assertThrows(() => {
+        instance.delete("");
+      });
+    });
+  });
+
+  describe("::has", () => {
+    let instance;
+
+    beforeEach(() => {
+      instance = new Storage();
+    });
+
+    it("[[Call]] returns whether the value was assigned", () => {
+      assertStrictEquals(instance.has("000-0000"), false);
+      instance.set("000-0000", new Storable());
+      assertStrictEquals(instance.has("000-0000"), true);
+      instance.delete("000-0000")
+      assertStrictEquals(instance.has("000-0000"), false);
+    });
+
+    it("[[Call]] throws if the provided identifier is invalid", () => {
+      assertThrows(() => {
+        instance.has("");
+      });
+    });
+  });
+
+  describe("::set", () => {
+    let instance;
+    let newID;
+
+    beforeEach(() => {
+      instance = new Storage();
+      newID = new Storage().add(new Storable());
+    });
+
+    it("[[Call]] returns the instance", () => {
+      const result = instance.set(newID, new Storable());
+      assertStrictEquals(result, instance);
+    });
+
+    it("[[Call]] stores data for retrieval later", () => {
+      const data = { my: "data" };
+      const storable = new Storable(data);
+      instance.set(newID, storable);
+      assertEquals(instance.get(newID).data, data);
+    });
+
+    it("[[Call]] updates existing data", () => {
+      const data = { my: "data" };
+      const storable = new Storable(data);
+      instance.set(newID, storable);
+      data.my = "new data"
+      instance.set(newID, storable);
+      assertEquals(instance.get(newID).data, data);
+    });
+
+    it("[[Call]] does not store non‐enumerable properties", () => {
+      const data = Object.create(null, {
+        gone: { enumerable: false },
+      });
+      const storable = new Storable(data);
+      instance.set(newID, storable);
+      assert(!("gone" in instance.get(newID).data));
+    });
+
+    it("[[Call]] does not store prototype properties", () => {
+      const data = Object.create({ gone: true });
+      const storable = new Storable(data);
+      instance.set(newID, storable);
+      assert(!("gone" in instance.get(newID).data));
+    });
+
+    it("[[Call]] throws if the provided identifier is invalid", () => {
+      assertThrows(() => {
+        instance.set("", new Storable());
+      });
+    });
+
+    it("[[Call]] throws if the provided value is not an object", () => {
+      assertThrows(() => {
+        instance.set(newID, "");
+      });
+    });
+
+    it("[[Call]] throws if the provided value does not implement `[Storage.toObject]`", () => {
+      assertThrows(() => {
+        instance.set(newID, Object.create(null));
+      });
+    });
+  });
+
+  describe("::size", () => {
+    let instance;
+
+    beforeEach(() => {
+      instance = new Storage();
+    });
+
+    it("[[Get]] returns the number of stored values", () => {
+      assertStrictEquals(instance.size, 0);
+      instance.add(new Storable());
+      assertStrictEquals(instance.size, 1);
+      const newID = instance.add(new Storable());
+      assertStrictEquals(instance.size, 2);
+      instance.set(newID, new Storable());
+      assertStrictEquals(instance.size, 2);
+    });
+
+    it("[[Get]] does not count deleted values", () => {
+      assertStrictEquals(instance.size, 0);
+      instance.add(new Storable());
+      assertStrictEquals(instance.size, 1);
+      const newID = instance.add(new Storable());
+      assertStrictEquals(instance.size, 2);
+      instance.delete(newID);
+      assertStrictEquals(instance.size, 1);
+    });
+
+    it("[[Set]] throws when setting", () => {
+      assertThrows(() => {
+        instance.size = 1;
+      });
+    });
+  });
+});
This page took 0.031816 seconds and 4 git commands to generate.