Skip to content
File

Blob: tests/client/stores/workspace-directory.test.ts

typescript113 lines
1import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2import { createFreshDb, deleteDb } from "@tests/client/util/idb";
3import { createMembershipSummary, createWorkspace } from "@tests/client/util/fixtures";
4import type { BlandDatabase, MemberWorkspaceRow } from "@/client/stores/db/bland-db";
5 
6let db: BlandDatabase;
7let directoryCommands: typeof import("@/client/stores/db/workspace-directory").directoryCommands;
8 
9async function readInRankOrder(): Promise<MemberWorkspaceRow[]> {
10 return db.memberWorkspaces.orderBy("rank").toArray();
11}
12 
13beforeEach(async () => {
14 vi.resetModules();
15 db = createFreshDb();
16 vi.doMock("@/client/stores/db/bland-db", async () => {
17 const actual = await vi.importActual<typeof import("@/client/stores/db/bland-db")>("@/client/stores/db/bland-db");
18 return { ...actual, db };
19 });
20 directoryCommands = (await import("@/client/stores/db/workspace-directory")).directoryCommands;
21});
22 
23afterEach(async () => {
24 await deleteDb(db);
25 vi.restoreAllMocks();
26});
27 
28describe("directoryCommands", () => {
29 it("replaceAll replaces every directory row in one transaction", async () => {
30 await directoryCommands.replaceAll([createMembershipSummary({ id: "ws-1" })]);
31 await directoryCommands.replaceAll([
32 createMembershipSummary({ id: "ws-2", slug: "second" }),
33 createMembershipSummary({ id: "ws-3", slug: "third" }),
34 ]);
35 const rows = (await db.memberWorkspaces.toArray()).map((r) => r.id).sort();
36 expect(rows).toEqual(["ws-2", "ws-3"]);
37 });
38 
39 it("upsert adds a new row and updates an existing row", async () => {
40 await directoryCommands.upsert(createMembershipSummary({ id: "ws-1", name: "First" }));
41 expect((await db.memberWorkspaces.get("ws-1"))?.name).toBe("First");
42 
43 await directoryCommands.upsert(createMembershipSummary({ id: "ws-1", name: "Renamed" }));
44 expect((await db.memberWorkspaces.get("ws-1"))?.name).toBe("Renamed");
45 });
46 
47 it("patch merges plain Workspace updates without dropping role", async () => {
48 await directoryCommands.upsert(createMembershipSummary({ id: "ws-1", name: "Old", role: "guest" }));
49 await directoryCommands.patch("ws-1", createWorkspace({ id: "ws-1", name: "Renamed" }));
50 const row = await db.memberWorkspaces.get("ws-1");
51 expect(row?.name).toBe("Renamed");
52 expect(row?.role).toBe("guest");
53 });
54 
55 it("patch no-ops when the row is absent", async () => {
56 await directoryCommands.patch("ws-missing", createWorkspace({ id: "ws-missing", name: "Ghost" }));
57 expect(await db.memberWorkspaces.get("ws-missing")).toBeUndefined();
58 });
59 
60 it("remove deletes by id", async () => {
61 await directoryCommands.replaceAll([
62 createMembershipSummary({ id: "ws-1" }),
63 createMembershipSummary({ id: "ws-2" }),
64 ]);
65 await directoryCommands.remove("ws-1");
66 const ids = (await db.memberWorkspaces.toArray()).map((r) => r.id);
67 expect(ids).toEqual(["ws-2"]);
68 });
69 
70 describe("durable ordering", () => {
71 it("replaceAll assigns a rank matching the source array index", async () => {
72 await directoryCommands.replaceAll([
73 createMembershipSummary({ id: "ws-c", slug: "c" }),
74 createMembershipSummary({ id: "ws-a", slug: "a" }),
75 createMembershipSummary({ id: "ws-b", slug: "b" }),
76 ]);
77 const rows = await readInRankOrder();
78 expect(rows.map((r) => r.id)).toEqual(["ws-c", "ws-a", "ws-b"]);
79 });
80 
81 it("upsert preserves an existing row's rank", async () => {
82 await directoryCommands.replaceAll([
83 createMembershipSummary({ id: "ws-1", slug: "one" }),
84 createMembershipSummary({ id: "ws-2", slug: "two" }),
85 ]);
86 await directoryCommands.upsert(createMembershipSummary({ id: "ws-1", slug: "one-renamed" }));
87 const rows = await readInRankOrder();
88 expect(rows.map((r) => r.id)).toEqual(["ws-1", "ws-2"]);
89 expect(rows.find((r) => r.id === "ws-1")?.slug).toBe("one-renamed");
90 });
91 
92 it("upsert appends a new workspace at max(rank) + 1", async () => {
93 await directoryCommands.replaceAll([
94 createMembershipSummary({ id: "ws-1" }),
95 createMembershipSummary({ id: "ws-2" }),
96 ]);
97 await directoryCommands.upsert(createMembershipSummary({ id: "ws-3" }));
98 const rows = await readInRankOrder();
99 expect(rows.map((r) => r.id)).toEqual(["ws-1", "ws-2", "ws-3"]);
100 });
101 
102 it("patch preserves rank alongside role", async () => {
103 await directoryCommands.replaceAll([
104 createMembershipSummary({ id: "ws-1", role: "guest" }),
105 createMembershipSummary({ id: "ws-2" }),
106 ]);
107 await directoryCommands.patch("ws-1", createWorkspace({ id: "ws-1", name: "Renamed" }));
108 const rows = await readInRankOrder();
109 expect(rows[0]).toMatchObject({ id: "ws-1", name: "Renamed", role: "guest", rank: 0 });
110 });
111 });
112});