Skip to content

Commit 3ea6a73

Browse files
test(web): cover instance store CRUD and legacy migration
Closes the multi-instance coverage gap: add (first becomes active, later adds don't steal focus, insertion order), switch (incl. unknown-id no-op), delete (active->first-remaining fallback, non-active unchanged, last clears active), update (patch + unknown-id no-op), active config, and legacy-key migration.
1 parent 96dff89 commit 3ea6a73

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
2+
import {
3+
addInstance,
4+
deleteInstance,
5+
getActiveInstance,
6+
loadConfig,
7+
loadStore,
8+
setActiveInstance,
9+
updateInstance,
10+
} from "@/lib/config";
11+
12+
const STORE_KEY = "openconcho:instances";
13+
const LEGACY_KEY = "openconcho:config";
14+
15+
beforeEach(() => localStorage.clear());
16+
afterEach(() => localStorage.clear());
17+
18+
describe("instance store — add + active selection", () => {
19+
it("makes the first added instance active", () => {
20+
const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
21+
expect(loadStore().activeId).toBe(a.id);
22+
});
23+
24+
it("does not steal active focus when adding more instances", () => {
25+
const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
26+
addInstance({ name: "B", baseUrl: "https://b.example.net", token: "" });
27+
expect(loadStore().activeId).toBe(a.id);
28+
});
29+
30+
it("appends instances in insertion order", () => {
31+
addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
32+
addInstance({ name: "B", baseUrl: "https://b.example.net", token: "" });
33+
expect(loadStore().instances.map((i) => i.name)).toEqual(["A", "B"]);
34+
});
35+
});
36+
37+
describe("instance store — switching active", () => {
38+
it("switches the active instance", () => {
39+
addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
40+
const b = addInstance({ name: "B", baseUrl: "https://b.example.net", token: "" });
41+
setActiveInstance(b.id);
42+
expect(getActiveInstance()?.id).toBe(b.id);
43+
});
44+
45+
it("ignores an unknown id", () => {
46+
const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
47+
setActiveInstance("does-not-exist");
48+
expect(getActiveInstance()?.id).toBe(a.id);
49+
});
50+
});
51+
52+
describe("instance store — deletion", () => {
53+
it("falls back to the first remaining when the active instance is deleted", () => {
54+
const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
55+
const b = addInstance({ name: "B", baseUrl: "https://b.example.net", token: "" });
56+
setActiveInstance(b.id);
57+
deleteInstance(b.id);
58+
expect(loadStore().activeId).toBe(a.id);
59+
});
60+
61+
it("leaves the active id unchanged when a non-active instance is deleted", () => {
62+
const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
63+
const b = addInstance({ name: "B", baseUrl: "https://b.example.net", token: "" });
64+
deleteInstance(b.id);
65+
expect(getActiveInstance()?.id).toBe(a.id);
66+
});
67+
68+
it("clears the active id when the last instance is removed", () => {
69+
const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
70+
deleteInstance(a.id);
71+
expect(loadStore().activeId).toBeNull();
72+
});
73+
74+
it("returns null config once every instance is gone", () => {
75+
const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
76+
deleteInstance(a.id);
77+
expect(loadConfig()).toBeNull();
78+
});
79+
});
80+
81+
describe("instance store — update", () => {
82+
it("patches the named fields", () => {
83+
const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
84+
updateInstance(a.id, { name: "Renamed", token: "sk-1" });
85+
expect(loadStore().instances[0]).toMatchObject({ name: "Renamed", token: "sk-1" });
86+
});
87+
88+
it("no-ops on an unknown id", () => {
89+
addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
90+
updateInstance("nope", { name: "X" });
91+
expect(loadStore().instances[0].name).toBe("A");
92+
});
93+
});
94+
95+
describe("instance store — active config", () => {
96+
it("reflects the active instance's url and token", () => {
97+
addInstance({ name: "A", baseUrl: "https://a.example.net", token: "sk-a" });
98+
expect(loadConfig()).toEqual({ baseUrl: "https://a.example.net", token: "sk-a" });
99+
});
100+
});
101+
102+
describe("instance store — legacy migration", () => {
103+
it("migrates the legacy single-config key into the instances store", () => {
104+
localStorage.setItem(
105+
LEGACY_KEY,
106+
JSON.stringify({ baseUrl: "https://legacy.example.net", token: "sk-legacy" }),
107+
);
108+
const store = loadStore();
109+
expect(store.instances[0]).toMatchObject({
110+
name: "Default",
111+
baseUrl: "https://legacy.example.net",
112+
token: "sk-legacy",
113+
});
114+
});
115+
116+
it("removes the legacy key after migrating", () => {
117+
localStorage.setItem(
118+
LEGACY_KEY,
119+
JSON.stringify({ baseUrl: "https://legacy.example.net", token: "" }),
120+
);
121+
loadStore();
122+
expect(localStorage.getItem(LEGACY_KEY)).toBeNull();
123+
expect(localStorage.getItem(STORE_KEY)).toBeTruthy();
124+
});
125+
});

0 commit comments

Comments
 (0)