aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/history.test.ts77
-rw-r--r--tests/operations.test.ts64
-rw-r--r--tests/storage.test.ts83
3 files changed, 224 insertions, 0 deletions
diff --git a/tests/history.test.ts b/tests/history.test.ts
new file mode 100644
index 0000000..ecd7785
--- /dev/null
+++ b/tests/history.test.ts
@@ -0,0 +1,77 @@
+
+
+
+/*** IMPORT ------------------------------------------- ***/
+
+import { assertEquals } from "jsr:@std/assert@^1.0.0";
+
+/*** UTILITY ------------------------------------------ ***/
+
+import { evict } from "../source/library/state/history-logic.ts";
+
+type Entry = {
+ favorite: boolean;
+ id: string;
+ timestamp: number;
+};
+
+function entry(id: string, timestamp: number, favorite = false): Entry {
+ return { favorite, id, timestamp };
+}
+
+/*** TESTS -------------------------------------------- ***/
+
+Deno.test("evict keeps everything when under cap", () => {
+ const entries = [entry("a", 3), entry("b", 2), entry("c", 1)];
+ assertEquals(evict(entries, 5), entries);
+});
+
+Deno.test("evict drops the oldest non-favorites above cap", () => {
+ const entries = [
+ entry("a", 5),
+ entry("b", 4),
+ entry("c", 3),
+ entry("d", 2),
+ entry("e", 1)
+ ];
+ const kept = evict(entries, 3);
+ assertEquals(kept.map((e) => e.id), ["a", "b", "c"]);
+});
+
+Deno.test("evict never drops favorites", () => {
+ const entries = [
+ entry("a", 10),
+ entry("b", 9),
+ entry("fav-old", 1, true),
+ entry("c", 8),
+ entry("d", 7)
+ ];
+ const kept = evict(entries, 3);
+
+ assertEquals(kept.some((e) => e.id === "fav-old"), true);
+ assertEquals(kept.length, 3);
+});
+
+Deno.test("evict can exceed cap when favorites alone do so", () => {
+ const entries = [
+ entry("fav-1", 5, true),
+ entry("fav-2", 4, true),
+ entry("fav-3", 3, true),
+ entry("regular", 2)
+ ];
+ const kept = evict(entries, 2);
+
+ assertEquals(kept.length, 3);
+ assertEquals(kept.every((e) => e.favorite), true);
+});
+
+Deno.test("evict sorts by timestamp descending", () => {
+ const entries = [
+ entry("c", 1),
+ entry("a", 3),
+ entry("b", 2),
+ entry("d", 0)
+ ];
+ const kept = evict(entries, 3);
+ assertEquals(kept.map((e) => e.id), ["a", "b", "c"]);
+});
diff --git a/tests/operations.test.ts b/tests/operations.test.ts
new file mode 100644
index 0000000..99357ea
--- /dev/null
+++ b/tests/operations.test.ts
@@ -0,0 +1,64 @@
+
+
+
+/*** IMPORT ------------------------------------------- ***/
+
+import { assertEquals } from "jsr:@std/assert@^1.0.0";
+
+/*** UTILITY ------------------------------------------ ***/
+
+import { deriveTitle, parseOperations } from "../source/library/graphql/operations.ts";
+
+/*** TESTS -------------------------------------------- ***/
+
+Deno.test("parseOperations returns empty for blank query", () => {
+ assertEquals(parseOperations(""), []);
+ assertEquals(parseOperations(" "), []);
+});
+
+Deno.test("parseOperations returns empty on syntax error", () => {
+ assertEquals(parseOperations("query { ..."), []);
+});
+
+Deno.test("parseOperations captures a single named query", () => {
+ const ops = parseOperations("query Foo { viewer { id } }");
+ assertEquals(ops, [{ name: "Foo", type: "query" }]);
+});
+
+Deno.test("parseOperations returns null name for anonymous ops", () => {
+ const ops = parseOperations("{ viewer { id } }");
+ assertEquals(ops, [{ name: null, type: "query" }]);
+});
+
+Deno.test("parseOperations captures multiple operations", () => {
+ const ops = parseOperations(`
+ query Foo { a }
+ mutation Bar { b }
+ subscription Baz { c }
+ `);
+ assertEquals(ops, [
+ { name: "Foo", type: "query" },
+ { name: "Bar", type: "mutation" },
+ { name: "Baz", type: "subscription" }
+ ]);
+});
+
+Deno.test("deriveTitle prefers the first operation name", () => {
+ const ops = parseOperations("query Foo { a }");
+ assertEquals(deriveTitle("query Foo { a }", ops), "Foo");
+});
+
+Deno.test("deriveTitle falls back to operation type", () => {
+ const ops = parseOperations("mutation { a }");
+ assertEquals(deriveTitle("mutation { a }", ops), "mutation");
+});
+
+Deno.test("deriveTitle falls back to the first 20 chars when unparsable", () => {
+ const query = "this is not valid graphql at all";
+ assertEquals(deriveTitle(query, parseOperations(query)), "this is not valid gr");
+});
+
+Deno.test("deriveTitle returns 'untitled' for empty input", () => {
+ assertEquals(deriveTitle("", []), "untitled");
+ assertEquals(deriveTitle(" ", []), "untitled");
+});
diff --git a/tests/storage.test.ts b/tests/storage.test.ts
new file mode 100644
index 0000000..7d6ba73
--- /dev/null
+++ b/tests/storage.test.ts
@@ -0,0 +1,83 @@
+
+
+
+/*** IMPORT ------------------------------------------- ***/
+
+import { assertEquals } from "jsr:@std/assert@^1.0.0";
+
+/*** UTILITY ------------------------------------------ ***/
+
+import {
+ createLocalStorage,
+ createMemoryStorage
+} from "../source/library/state/storage.ts";
+
+/*** TESTS -------------------------------------------- ***/
+
+Deno.test("memory storage round-trips objects", () => {
+ const storage = createMemoryStorage();
+ storage.set("k", { hello: "world" });
+ assertEquals(storage.get<{ hello: string }>("k"), { hello: "world" });
+});
+
+Deno.test("memory storage returns null for missing keys", () => {
+ const storage = createMemoryStorage();
+ assertEquals(storage.get("missing"), null);
+});
+
+Deno.test("memory storage remove clears a key", () => {
+ const storage = createMemoryStorage();
+ storage.set("k", 42);
+ storage.remove("k");
+ assertEquals(storage.get("k"), null);
+});
+
+Deno.test("memory storage instances are isolated", () => {
+ const a = createMemoryStorage();
+ const b = createMemoryStorage();
+ a.set("shared", 1);
+ assertEquals(b.get("shared"), null);
+});
+
+Deno.test("local storage namespaces keys", () => {
+ globalThis.localStorage.clear();
+
+ const alpha = createLocalStorage("alpha");
+ const beta = createLocalStorage("beta");
+
+ alpha.set("shared", { tag: "a" });
+ beta.set("shared", { tag: "b" });
+
+ assertEquals(alpha.get<{ tag: string }>("shared"), { tag: "a" });
+ assertEquals(beta.get<{ tag: string }>("shared"), { tag: "b" });
+ assertEquals(globalThis.localStorage.getItem("alpha:shared"), JSON.stringify({ tag: "a" }));
+ assertEquals(globalThis.localStorage.getItem("beta:shared"), JSON.stringify({ tag: "b" }));
+
+ globalThis.localStorage.clear();
+});
+
+Deno.test("local storage remove respects the namespace", () => {
+ globalThis.localStorage.clear();
+
+ const alpha = createLocalStorage("alpha");
+ const beta = createLocalStorage("beta");
+
+ alpha.set("k", 1);
+ beta.set("k", 2);
+
+ alpha.remove("k");
+ assertEquals(alpha.get("k"), null);
+ assertEquals(beta.get<number>("k"), 2);
+
+ globalThis.localStorage.clear();
+});
+
+Deno.test("local storage returns null on malformed JSON", () => {
+ globalThis.localStorage.clear();
+ globalThis.localStorage.setItem("alpha:bad", "not-json");
+
+ const alpha = createLocalStorage("alpha");
+ assertEquals(alpha.get("bad"), null);
+
+ globalThis.localStorage.clear();
+});