aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authornetop://ウィビ <paul@webb.page>2026-04-26 20:18:30 -0700
committernetop://ウィビ <paul@webb.page>2026-04-26 20:18:30 -0700
commit3c06c95f396b6e911076bc3291d5855ed01b5caa (patch)
tree17cd218339c52fbeee93d931303b04a3ff294f8b /tests
parentf059d97ab7f6d74d61139ac698cb871be7cb632e (diff)
downloadgraphiql-3c06c95f396b6e911076bc3291d5855ed01b5caa.tar.gz
graphiql-3c06c95f396b6e911076bc3291d5855ed01b5caa.zip
cleanup and ready for launch
Diffstat (limited to 'tests')
-rw-r--r--tests/apq.test.ts70
-rw-r--r--tests/format.test.ts3
-rw-r--r--tests/history.test.ts18
-rw-r--r--tests/keyboard.test.ts24
-rw-r--r--tests/operations.test.ts8
-rw-r--r--tests/session-io.test.ts51
-rw-r--r--tests/storage.test.ts80
-rw-r--r--tests/timing.test.ts21
8 files changed, 154 insertions, 121 deletions
diff --git a/tests/apq.test.ts b/tests/apq.test.ts
index 66607cd..2097377 100644
--- a/tests/apq.test.ts
+++ b/tests/apq.test.ts
@@ -10,43 +10,9 @@ import { expect, test } from "vitest";
import { createApqFetcher } from "../source/library/fetcher/apq.ts";
-/*** HELPERS ------------------------------------------ ***/
-
type Call = { body: Record<string, unknown>; url: string };
-function createStub(queue: Array<Record<string, unknown>>): {
- calls: Call[];
- stub: typeof fetch;
-} {
- const calls: Call[] = [];
-
- const stub: typeof fetch = (input, init) => {
- const body = JSON.parse((init?.body as string) ?? "null") as Record<string, unknown>;
- calls.push({ body, url: String(input) });
-
- const next = queue.shift();
-
- if (next === undefined)
- throw new Error("stub fetch exhausted");
-
- return Promise.resolve(new Response(JSON.stringify(next), { status: 200 }));
- };
-
- return { calls, stub };
-}
-
-async function expectedHash(query: string): Promise<string> {
- const buf = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(query));
- return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
-}
-
-function readPersistedHash(body: Record<string, unknown>): string {
- const extensions = body.extensions as Record<string, unknown>;
- const persistedQuery = extensions.persistedQuery as Record<string, unknown>;
- return persistedQuery.sha256Hash as string;
-}
-
-/*** TESTS -------------------------------------------- ***/
+/*** PROGRAM ------------------------------------------ ***/
test("apq cache miss retries with the full query", async () => {
const { calls, stub } = createStub([
@@ -149,3 +115,37 @@ test("apq accepts PERSISTED_QUERY_NOT_FOUND extension code", async () => {
expect(calls.length).toEqual(2);
expect(calls[1].body.query).toEqual("{ hello }");
});
+
+/*** HELPER ------------------------------------------- ***/
+
+function createStub(queue: Array<Record<string, unknown>>): {
+ calls: Call[];
+ stub: typeof fetch;
+} {
+ const calls: Call[] = [];
+
+ const stub: typeof fetch = (input, init) => {
+ const body = JSON.parse((init?.body as string) ?? "null") as Record<string, unknown>;
+ calls.push({ body, url: String(input) });
+
+ const next = queue.shift();
+
+ if (next === undefined)
+ throw new Error("stub fetch exhausted");
+
+ return Promise.resolve(new Response(JSON.stringify(next), { status: 200 }));
+ };
+
+ return { calls, stub };
+}
+
+async function expectedHash(query: string): Promise<string> {
+ const buf = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(query));
+ return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
+}
+
+function readPersistedHash(body: Record<string, unknown>): string {
+ const extensions = body.extensions as Record<string, unknown>;
+ const persistedQuery = extensions.persistedQuery as Record<string, unknown>;
+ return persistedQuery.sha256Hash as string;
+}
diff --git a/tests/format.test.ts b/tests/format.test.ts
index ae5d40c..e0b9f1b 100644
--- a/tests/format.test.ts
+++ b/tests/format.test.ts
@@ -10,11 +10,12 @@ import { expect, test } from "vitest";
import { format } from "../source/library/graphql/format.ts";
-/*** TESTS -------------------------------------------- ***/
+/*** PROGRAM ------------------------------------------ ***/
test("format pretty-prints a valid query", () => {
const input = "query Foo{viewer{id name}}";
const expected = "query Foo {\n viewer {\n id\n name\n }\n}";
+
expect(format(input)).toEqual(expected);
});
diff --git a/tests/history.test.ts b/tests/history.test.ts
index a08c014..581c0ef 100644
--- a/tests/history.test.ts
+++ b/tests/history.test.ts
@@ -16,11 +16,7 @@ type Entry = {
timestamp: number;
};
-function entry(id: string, timestamp: number, favorite = false): Entry {
- return { favorite, id, timestamp };
-}
-
-/*** TESTS -------------------------------------------- ***/
+/*** PROGRAM ------------------------------------------ ***/
test("evict keeps everything when under cap", () => {
const entries = [entry("a", 3), entry("b", 2), entry("c", 1)];
@@ -35,7 +31,9 @@ test("evict drops the oldest non-favorites above cap", () => {
entry("d", 2),
entry("e", 1)
];
+
const kept = evict(entries, 3);
+
expect(kept.map((e) => e.id)).toEqual(["a", "b", "c"]);
});
@@ -47,6 +45,7 @@ test("evict never drops favorites", () => {
entry("c", 8),
entry("d", 7)
];
+
const kept = evict(entries, 3);
expect(kept.some((e) => e.id === "fav-old")).toEqual(true);
@@ -60,6 +59,7 @@ test("evict can exceed cap when favorites alone do so", () => {
entry("fav-3", 3, true),
entry("regular", 2)
];
+
const kept = evict(entries, 2);
expect(kept.length).toEqual(3);
@@ -73,6 +73,14 @@ test("evict sorts by timestamp descending", () => {
entry("b", 2),
entry("d", 0)
];
+
const kept = evict(entries, 3);
+
expect(kept.map((e) => e.id)).toEqual(["a", "b", "c"]);
});
+
+/*** HELPER ------------------------------------------- ***/
+
+function entry(id: string, timestamp: number, favorite = false): Entry {
+ return { favorite, id, timestamp };
+}
diff --git a/tests/keyboard.test.ts b/tests/keyboard.test.ts
index 3a7f3cc..6550e98 100644
--- a/tests/keyboard.test.ts
+++ b/tests/keyboard.test.ts
@@ -18,17 +18,7 @@ type EventInit = {
shiftKey?: boolean;
};
-function makeEvent(init: EventInit): KeyboardEvent {
- return {
- altKey: init.altKey ?? false,
- ctrlKey: init.ctrlKey ?? false,
- key: init.key,
- metaKey: init.metaKey ?? false,
- shiftKey: init.shiftKey ?? false
- } as KeyboardEvent;
-}
-
-/*** TESTS -------------------------------------------- ***/
+/*** PROGRAM ------------------------------------------ ***/
test("matchShortcut returns null for plain Enter", () => {
expect(matchShortcut(makeEvent({ key: "Enter" }))).toEqual(null);
@@ -109,3 +99,15 @@ test("matchShortcut ignores Cmd+Alt+Enter", () => {
matchShortcut(makeEvent({ altKey: true, key: "Enter", metaKey: true }))
).toEqual(null);
});
+
+/*** HELPER ------------------------------------------- ***/
+
+function makeEvent(init: EventInit): KeyboardEvent {
+ return {
+ altKey: init.altKey ?? false,
+ ctrlKey: init.ctrlKey ?? false,
+ key: init.key,
+ metaKey: init.metaKey ?? false,
+ shiftKey: init.shiftKey ?? false
+ } as KeyboardEvent;
+}
diff --git a/tests/operations.test.ts b/tests/operations.test.ts
index 14fe768..6acca31 100644
--- a/tests/operations.test.ts
+++ b/tests/operations.test.ts
@@ -8,9 +8,12 @@ import { expect, test } from "vitest";
/*** UTILITY ------------------------------------------ ***/
-import { deriveTitle, parseOperations } from "../source/library/graphql/operations.ts";
+import {
+ deriveTitle,
+ parseOperations
+} from "../source/library/graphql/operations.ts";
-/*** TESTS -------------------------------------------- ***/
+/*** PROGRAM ------------------------------------------ ***/
test("parseOperations returns empty for blank query", () => {
expect(parseOperations("")).toEqual([]);
@@ -37,6 +40,7 @@ test("parseOperations captures multiple operations", () => {
mutation Bar { b }
subscription Baz { c }
`);
+
expect(ops).toEqual([
{ name: "Foo", type: "query" },
{ name: "Bar", type: "mutation" },
diff --git a/tests/session-io.test.ts b/tests/session-io.test.ts
index 6de919b..88c1ee6 100644
--- a/tests/session-io.test.ts
+++ b/tests/session-io.test.ts
@@ -16,29 +16,7 @@ import {
type TabInput = Parameters<typeof tabToExport>[0];
-function validExport(): SessionExport {
- return {
- exportedAt: "2026-04-24T00:00:00.000Z",
- tabs: [
- {
- headers: "{}",
- operationName: "MyOp",
- query: "query MyOp { hello }",
- title: "MyOp",
- variables: "{}"
- }
- ],
- version: 1
- };
-}
-
-function isError(result: unknown): result is { error: string } {
- return typeof result === "object" &&
- result !== null &&
- "error" in result;
-}
-
-/*** TESTS -------------------------------------------- ***/
+/*** PROGRAM ------------------------------------------ ***/
test("validateSessionExport round-trips a valid payload unchanged", () => {
const data = validExport();
@@ -58,6 +36,7 @@ test("validateSessionExport rejects non-object input", () => {
test("validateSessionExport rejects wrong version", () => {
const zero = validateSessionExport({ ...validExport(), version: 0 });
const two = validateSessionExport({ ...validExport(), version: 2 });
+
const missing = validateSessionExport({
exportedAt: "2026-04-24T00:00:00.000Z",
tabs: []
@@ -142,6 +121,7 @@ test("validateSessionExport accepts null operationName", () => {
test("validateSessionExport rejects string field > 1 MB", () => {
const big = "x".repeat(1024 * 1024 + 1);
+
const result = validateSessionExport({
exportedAt: "2026-04-24T00:00:00.000Z",
tabs: [
@@ -167,6 +147,7 @@ test("validateSessionExport rejects > 50 tabs", () => {
title: "t",
variables: "{}"
}));
+
const result = validateSessionExport({
exportedAt: "2026-04-24T00:00:00.000Z",
tabs,
@@ -199,3 +180,27 @@ test("tabToExport strips id, result, operations, titleDirty", () => {
variables: "{}"
});
});
+
+/*** HELPER ------------------------------------------- ***/
+
+function isError(result: unknown): result is { error: string } {
+ return typeof result === "object" &&
+ result !== null &&
+ "error" in result;
+}
+
+function validExport(): SessionExport {
+ return {
+ exportedAt: "2026-04-24T00:00:00.000Z",
+ tabs: [
+ {
+ headers: "{}",
+ operationName: "MyOp",
+ query: "query MyOp { hello }",
+ title: "MyOp",
+ variables: "{}"
+ }
+ ],
+ version: 1
+ };
+}
diff --git a/tests/storage.test.ts b/tests/storage.test.ts
index 434a67d..3fbbdd9 100644
--- a/tests/storage.test.ts
+++ b/tests/storage.test.ts
@@ -13,47 +13,11 @@ import {
createMemoryStorage
} from "../source/library/state/storage.ts";
-/*** HELPERS ------------------------------------------ ***/
-
-function installLocalStorage(): void {
- if (typeof globalThis.localStorage !== "undefined")
- return;
-
- const store = new Map<string, string>();
-
- const shim: Storage = {
- clear(): void {
- store.clear();
- },
- getItem(key: string): string | null {
- return store.has(key) ? store.get(key) ?? null : null;
- },
- key(index: number): string | null {
- return Array.from(store.keys())[index] ?? null;
- },
- get length(): number {
- return store.size;
- },
- removeItem(key: string): void {
- store.delete(key);
- },
- setItem(key: string, value: string): void {
- store.set(key, String(value));
- }
- };
-
- Object.defineProperty(globalThis, "localStorage", {
- configurable: true,
- value: shim,
- writable: true
- });
-}
-
beforeAll(() => {
installLocalStorage();
});
-/*** TESTS -------------------------------------------- ***/
+/*** PROGRAM ------------------------------------------ ***/
test("memory storage round-trips objects", () => {
const storage = createMemoryStorage();
@@ -68,15 +32,19 @@ test("memory storage returns null for missing keys", () => {
test("memory storage remove clears a key", () => {
const storage = createMemoryStorage();
+
storage.set("k", 42);
storage.remove("k");
+
expect(storage.get("k")).toEqual(null);
});
test("memory storage instances are isolated", () => {
const a = createMemoryStorage();
const b = createMemoryStorage();
+
a.set("shared", 1);
+
expect(b.get("shared")).toEqual(null);
});
@@ -105,8 +73,8 @@ test("local storage remove respects the namespace", () => {
alpha.set("k", 1);
beta.set("k", 2);
-
alpha.remove("k");
+
expect(alpha.get("k")).toEqual(null);
expect(beta.get<number>("k")).toEqual(2);
@@ -122,3 +90,39 @@ test("local storage returns null on malformed JSON", () => {
globalThis.localStorage.clear();
});
+
+/*** HELPER ------------------------------------------- ***/
+
+function installLocalStorage(): void {
+ if (typeof globalThis.localStorage !== "undefined")
+ return;
+
+ const store = new Map<string, string>();
+
+ const shim: Storage = {
+ clear(): void {
+ store.clear();
+ },
+ getItem(key: string): string | null {
+ return store.has(key) ? store.get(key) ?? null : null;
+ },
+ key(index: number): string | null {
+ return Array.from(store.keys())[index] ?? null;
+ },
+ get length(): number {
+ return store.size;
+ },
+ removeItem(key: string): void {
+ store.delete(key);
+ },
+ setItem(key: string, value: string): void {
+ store.set(key, String(value));
+ }
+ };
+
+ Object.defineProperty(globalThis, "localStorage", {
+ configurable: true,
+ value: shim,
+ writable: true
+ });
+}
diff --git a/tests/timing.test.ts b/tests/timing.test.ts
index e2a0a06..589443d 100644
--- a/tests/timing.test.ts
+++ b/tests/timing.test.ts
@@ -8,15 +8,15 @@ import { expect, test } from "vitest";
/*** UTILITY ------------------------------------------ ***/
-import type { Fetcher, FetcherResult } from "../source/library/fetcher/types.ts";
-import { SessionStore } from "../source/library/state/session.svelte.ts";
import { createMemoryStorage } from "../source/library/state/storage.ts";
+import { SessionStore } from "../source/library/state/session.svelte.ts";
-function delay(ms: number): Promise<void> {
- return new Promise((resolve) => setTimeout(resolve, ms));
-}
+import {
+ type Fetcher,
+ type FetcherResult
+} from "../source/library/fetcher/types.ts";
-/*** TESTS -------------------------------------------- ***/
+/*** PROGRAM ------------------------------------------ ***/
test("run() populates timing for a one-shot fetcher", async () => {
const store = new SessionStore(createMemoryStorage());
@@ -54,8 +54,10 @@ test("run() records intervals between async iterable payloads", async () => {
async function* stream(): AsyncGenerator<FetcherResult> {
yield { data: { n: 1 } };
await delay(5);
+
yield { data: { n: 2 } };
await delay(5);
+
yield { data: { n: 3 } };
}
@@ -88,6 +90,7 @@ test("run() resets timing and streamIntervals on each invocation", async () => {
async function* stream(): AsyncGenerator<FetcherResult> {
yield { data: { n: 1 } };
await delay(5);
+
yield { data: { n: 2 } };
}
@@ -98,3 +101,9 @@ test("run() resets timing and streamIntervals on each invocation", async () => {
expect(tab.streamIntervals.length).toEqual(0);
expect(tab.timing).not.toBeNull();
});
+
+/*** HELPER ------------------------------------------- ***/
+
+function delay(ms: number): Promise<void> {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}