Files
kandev/apps/web/lib/local-storage.test.ts
T
José Almeida 3e237e177a feat: add agent-authored code walkthroughs (#1647)
* feat: add code-walkthrough backend (MCP tool, service, persistence, WS events)

* feat: render agent code walkthroughs as line-anchored cards + editor window

* test: add code-walkthrough re-emit mock-agent scenario

* fix: rework code-walkthrough UX (floating card, line reveal, minimize, clickable file)

* fix: improve code walkthrough editor UX

* fix: backfill walkthrough launcher after websocket connect

* fix: improve walkthrough requests and editor anchors

* fix: clear walkthrough highlights on close

* feat: add walkthrough discard and prompt customization

* fix: send walkthrough requests through saved prompt

* fix: support postgres built-in prompt seeding

* fix: address walkthrough review feedback

* fix: address walkthrough review follow-ups

* fix: harden walkthrough lifecycle races

* fix: reuse walkthrough notification cleanup

* fix: guard stale walkthrough file opens

* fix: abort stale file opens before paint

* fix: stabilize github settings e2e readiness

* fix: use stable github settings e2e selector
2026-07-10 08:34:57 +01:00

194 lines
5.3 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import {
cleanupTaskStorage,
clearGlobalSidebarWidth,
getGlobalSidebarWidth,
getOpenFileTabs,
markPRClosedBannerDismissed,
markPRMergedBannerDismissed,
restoreAttachmentPreview,
setGlobalSidebarWidth,
setOpenFileTabs,
wasPRClosedBannerDismissed,
wasPRMergedBannerDismissed,
} from "./local-storage";
describe("PR merged banner dismissal storage", () => {
beforeEach(() => {
window.sessionStorage.clear();
});
it("returns false when no dismissal has been recorded", () => {
expect(wasPRMergedBannerDismissed("task-a")).toBe(false);
});
it("persists dismissal per task and reads it back", () => {
markPRMergedBannerDismissed("task-a");
expect(wasPRMergedBannerDismissed("task-a")).toBe(true);
expect(wasPRMergedBannerDismissed("task-b")).toBe(false);
});
it("clears the dismissal flag via cleanupTaskStorage", () => {
markPRMergedBannerDismissed("task-a");
markPRMergedBannerDismissed("task-b");
cleanupTaskStorage("task-a", []);
expect(wasPRMergedBannerDismissed("task-a")).toBe(false);
expect(wasPRMergedBannerDismissed("task-b")).toBe(true);
});
});
describe("PR closed banner dismissal storage", () => {
beforeEach(() => {
window.sessionStorage.clear();
});
it("returns false when no dismissal has been recorded", () => {
expect(wasPRClosedBannerDismissed("task-a")).toBe(false);
});
it("persists dismissal per task and reads it back", () => {
markPRClosedBannerDismissed("task-a");
expect(wasPRClosedBannerDismissed("task-a")).toBe(true);
expect(wasPRClosedBannerDismissed("task-b")).toBe(false);
});
it("is independent from the merged banner dismissal flag", () => {
markPRMergedBannerDismissed("task-a");
expect(wasPRClosedBannerDismissed("task-a")).toBe(false);
});
it("clears the dismissal flag via cleanupTaskStorage", () => {
markPRClosedBannerDismissed("task-a");
markPRClosedBannerDismissed("task-b");
cleanupTaskStorage("task-a", []);
expect(wasPRClosedBannerDismissed("task-a")).toBe(false);
expect(wasPRClosedBannerDismissed("task-b")).toBe(true);
});
});
describe("global sidebar width storage", () => {
beforeEach(() => {
window.localStorage.clear();
});
it("returns null when no width has been stored", () => {
expect(getGlobalSidebarWidth()).toBeNull();
});
it("persists a width globally and reads it back (rounded)", () => {
setGlobalSidebarWidth(412.6);
expect(getGlobalSidebarWidth()).toBe(413);
});
it("ignores non-positive or non-finite widths", () => {
setGlobalSidebarWidth(0);
setGlobalSidebarWidth(-100);
setGlobalSidebarWidth(Number.NaN);
expect(getGlobalSidebarWidth()).toBeNull();
});
it("clears the stored width", () => {
setGlobalSidebarWidth(320);
clearGlobalSidebarWidth();
expect(getGlobalSidebarWidth()).toBeNull();
});
it("is NOT removed by cleanupTaskStorage (it is global, not task-scoped)", () => {
setGlobalSidebarWidth(320);
cleanupTaskStorage("task-a", []);
expect(getGlobalSidebarWidth()).toBe(320);
});
});
describe("task-scoped artifact notification storage", () => {
beforeEach(() => {
window.localStorage.clear();
});
it("clears walkthrough last-seen state via cleanupTaskStorage", () => {
window.localStorage.setItem(
"kandev.walkthrough.lastSeenByTask",
JSON.stringify({ "task-a": "2026-01-01T00:00:00Z", "task-b": "2026-01-02T00:00:00Z" }),
);
cleanupTaskStorage("task-a", []);
expect(
JSON.parse(window.localStorage.getItem("kandev.walkthrough.lastSeenByTask") ?? "{}"),
).toEqual({
"task-b": "2026-01-02T00:00:00Z",
});
});
});
describe("open file tabs storage", () => {
beforeEach(() => {
window.sessionStorage.clear();
});
it("round-trips the multi-repo repo subpath so a restored tab refetches under the right repo", () => {
setOpenFileTabs("sess-1", [
{
path: "src/foo.ts",
name: "foo.ts",
repo: "enrichment-commons",
markdownPreview: true,
pinned: true,
},
]);
const tabs = getOpenFileTabs("sess-1");
expect(tabs).toHaveLength(1);
expect(tabs[0]).toEqual({
path: "src/foo.ts",
name: "foo.ts",
repo: "enrichment-commons",
markdownPreview: true,
pinned: true,
});
});
it("leaves repo undefined for single-repo tabs", () => {
setOpenFileTabs("sess-1", [{ path: "src/foo.ts", name: "foo.ts", pinned: true }]);
expect(getOpenFileTabs("sess-1")[0].repo).toBeUndefined();
});
});
describe("chat draft attachment storage", () => {
it("normalizes invalid restored image delivery modes to prompt", () => {
const restored = restoreAttachmentPreview({
id: "att-1",
data: "abc",
mimeType: "image/png",
fileName: "shot.png",
size: 3,
isImage: true,
deliveryMode: "inline" as "prompt",
});
expect(restored.deliveryMode).toBe("prompt");
expect(restored.preview).toBe("data:image/png;base64,abc");
});
it("normalizes invalid restored file delivery modes to path", () => {
const restored = restoreAttachmentPreview({
id: "att-2",
data: "abc",
mimeType: "application/pdf",
fileName: "doc.pdf",
size: 3,
isImage: false,
deliveryMode: "inline" as "path",
});
expect(restored.deliveryMode).toBe("path");
});
});