story-kit: accept 145_story_persist_chat_history_to_localstorage_across_rebuilds
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
waitFor,
|
||||
} from "@testing-library/react";
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { api } from "../api/client";
|
||||
import type { Message } from "../types";
|
||||
import { Chat } from "./Chat";
|
||||
@@ -395,6 +395,103 @@ describe("Chat reconciliation banner", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Chat localStorage persistence (Story 145)", () => {
|
||||
const PROJECT_PATH = "/tmp/project";
|
||||
const STORAGE_KEY = `storykit-chat-history:${PROJECT_PATH}`;
|
||||
|
||||
beforeEach(() => {
|
||||
capturedWsHandlers = null;
|
||||
localStorage.clear();
|
||||
setupMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it("AC1: restores persisted messages on mount", async () => {
|
||||
const saved: Message[] = [
|
||||
{ role: "user", content: "Previously saved question" },
|
||||
{ role: "assistant", content: "Previously saved answer" },
|
||||
];
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(saved));
|
||||
|
||||
render(<Chat projectPath={PROJECT_PATH} onCloseProject={vi.fn()} />);
|
||||
|
||||
expect(
|
||||
await screen.findByText("Previously saved question"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText("Previously saved answer"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("AC2: persists messages when WebSocket onUpdate fires", async () => {
|
||||
render(<Chat projectPath={PROJECT_PATH} onCloseProject={vi.fn()} />);
|
||||
|
||||
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||
|
||||
const history: Message[] = [
|
||||
{ role: "user", content: "Hello" },
|
||||
{ role: "assistant", content: "Hi there!" },
|
||||
];
|
||||
|
||||
act(() => {
|
||||
capturedWsHandlers?.onUpdate(history);
|
||||
});
|
||||
|
||||
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "[]");
|
||||
expect(stored).toEqual(history);
|
||||
});
|
||||
|
||||
it("AC3: clears localStorage when New Session is clicked", async () => {
|
||||
const saved: Message[] = [
|
||||
{ role: "user", content: "Old message" },
|
||||
{ role: "assistant", content: "Old reply" },
|
||||
];
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(saved));
|
||||
|
||||
// Stub window.confirm to auto-approve the clear dialog
|
||||
const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(true);
|
||||
|
||||
render(<Chat projectPath={PROJECT_PATH} onCloseProject={vi.fn()} />);
|
||||
|
||||
// Wait for the persisted message to appear
|
||||
expect(await screen.findByText("Old message")).toBeInTheDocument();
|
||||
|
||||
// Click "New Session" button
|
||||
const newSessionBtn = screen.getByText(/New Session/);
|
||||
await act(async () => {
|
||||
fireEvent.click(newSessionBtn);
|
||||
});
|
||||
|
||||
// localStorage should be cleared
|
||||
expect(localStorage.getItem(STORAGE_KEY)).toBeNull();
|
||||
|
||||
// Messages should be gone from the UI
|
||||
expect(screen.queryByText("Old message")).not.toBeInTheDocument();
|
||||
|
||||
confirmSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("AC5: uses project-scoped storage key", async () => {
|
||||
const otherKey = "storykit-chat-history:/other/project";
|
||||
localStorage.setItem(
|
||||
otherKey,
|
||||
JSON.stringify([{ role: "user", content: "Other project msg" }]),
|
||||
);
|
||||
|
||||
render(<Chat projectPath={PROJECT_PATH} onCloseProject={vi.fn()} />);
|
||||
|
||||
// Should NOT show the other project's messages
|
||||
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||
expect(screen.queryByText("Other project msg")).not.toBeInTheDocument();
|
||||
|
||||
// Other project's data should still be in storage
|
||||
expect(localStorage.getItem(otherKey)).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Chat activity status indicator (Bug 140)", () => {
|
||||
beforeEach(() => {
|
||||
capturedWsHandlers = null;
|
||||
|
||||
Reference in New Issue
Block a user