Files
kandev/apps/web/components/task/dockview-shared.test.tsx
T
Clément Fiere 0e6c98c0a0 feat(web): add prompt history panel (#2738)
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
2026-08-18 16:13:15 +00:00

172 lines
6.4 KiB
TypeScript

import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
const mockAppState = vi.hoisted(() => ({
tasks: { activeSessionId: "session-1", activeTaskId: "task-1" },
sessionTodos: {
bySessionId: {} as Record<string, Array<{ description: string; status: string }>>,
},
taskSessions: { items: {} },
agentProfiles: { items: [] },
kanban: { tasks: [] },
kanbanMulti: { snapshots: {} },
}));
// See dockview-panel-content.todos.test.tsx for why this fallback exists: a
// session that already completed before the panel mounts only has its todo
// data in persisted message history, since `sessionTodos.bySessionId` is
// populated exclusively by a live WS handler and is never backfilled.
const mockMessages = vi.hoisted(() => ({ items: [] as Array<Record<string, unknown>> }));
const { mockScrollTranscriptToMessage } = vi.hoisted(() => ({
mockScrollTranscriptToMessage: vi.fn(),
}));
const promptHistoryProps = vi.hoisted(() => ({
current: null as null | { onNavigateToPrompt?: (messageId: string) => void },
}));
vi.mock("@/hooks/domains/session/use-session-messages", () => ({
useSessionMessages: () => ({ messages: mockMessages.items }),
}));
vi.mock("@/lib/state/dockview-store", () => ({
useDockviewStore: (selector: (state: Record<string, unknown>) => unknown) =>
selector({
selectedDiff: null,
setSelectedDiff: vi.fn(),
api: { getPanel: vi.fn(), removePanel: vi.fn() },
scrollTranscriptToMessage: mockScrollTranscriptToMessage,
}),
}));
vi.mock("./prompt-history-panel-content", () => ({
PromptHistoryPanelContent: (props: { onNavigateToPrompt?: (messageId: string) => void }) => {
promptHistoryProps.current = props;
return <div data-testid="prompt-history-mock" />;
},
}));
vi.mock("@/components/state-provider", () => ({
useAppStore: (selector: (state: Record<string, unknown>) => unknown) => selector(mockAppState),
useAppStoreApi: () => ({ getState: () => mockAppState }),
}));
import { renderPanel, VALID_COMPONENTS } from "./dockview-shared";
afterEach(() => {
cleanup();
promptHistoryProps.current = null;
mockScrollTranscriptToMessage.mockClear();
});
const WRITE_TESTS = "Write tests";
const IMPLEMENT_FEATURE = "Implement feature";
const SESSION_ID = "session-1";
const MESSAGE_ID = "message-1";
const CUSTOM_AGENT = "Custom Agent";
const PROMPT_HISTORY_COMPONENT = "prompt-history";
const PROMPT_HISTORY_MOCK = "prompt-history-mock";
describe("dockview prompt history panel (Office task layout)", () => {
it("accepts the prompt-history component in the shared registry", () => {
expect(VALID_COMPONENTS.has(PROMPT_HISTORY_COMPONENT)).toBe(true);
});
it("renders the prompt-history panel content for the registered component", () => {
mockAppState.taskSessions.items = { [SESSION_ID]: { name: CUSTOM_AGENT } };
render(<>{renderPanel(PROMPT_HISTORY_MOCK, PROMPT_HISTORY_COMPONENT, {})}</>);
expect(screen.getByTestId(PROMPT_HISTORY_MOCK)).toBeTruthy();
});
it("binds the arrow to scrollTranscriptToMessage with a custom session name", () => {
mockAppState.taskSessions.items = { [SESSION_ID]: { name: CUSTOM_AGENT } };
render(<>{renderPanel(PROMPT_HISTORY_MOCK, PROMPT_HISTORY_COMPONENT, {})}</>);
promptHistoryProps.current?.onNavigateToPrompt?.(MESSAGE_ID);
expect(mockScrollTranscriptToMessage).toHaveBeenCalledWith(
SESSION_ID,
MESSAGE_ID,
CUSTOM_AGENT,
);
});
it("falls back to the chat panel title for an EMPTY-string session name", () => {
mockAppState.taskSessions.items = { [SESSION_ID]: { name: "" } };
render(<>{renderPanel(PROMPT_HISTORY_MOCK, PROMPT_HISTORY_COMPONENT, {})}</>);
promptHistoryProps.current?.onNavigateToPrompt?.(MESSAGE_ID);
expect(mockScrollTranscriptToMessage).toHaveBeenCalledWith(SESSION_ID, MESSAGE_ID, "Agent");
});
it("falls back to the chat panel title when the session name is absent", () => {
mockAppState.taskSessions.items = { [SESSION_ID]: {} };
render(<>{renderPanel(PROMPT_HISTORY_MOCK, PROMPT_HISTORY_COMPONENT, {})}</>);
promptHistoryProps.current?.onNavigateToPrompt?.(MESSAGE_ID);
expect(mockScrollTranscriptToMessage).toHaveBeenCalledWith(SESSION_ID, MESSAGE_ID, "Agent");
});
});
describe("dockview todos panel content", () => {
it("renders the same checklist rows, order, and status semantics the todo indicator shows", () => {
mockAppState.sessionTodos.bySessionId = {
"session-1": [
{ description: WRITE_TESTS, status: "completed" },
{ description: IMPLEMENT_FEATURE, status: "in_progress" },
],
};
mockMessages.items = [];
render(<>{renderPanel("todos-panel", "todos", {})}</>);
const rows = screen.getAllByText(new RegExp(`${WRITE_TESTS}|${IMPLEMENT_FEATURE}`));
expect(rows.map((row) => row.textContent)).toEqual([WRITE_TESTS, IMPLEMENT_FEATURE]);
// Same progress header TodoIndicator shows: 1 of 2 entries completed.
expect(screen.getByText("1/2 completed")).toBeTruthy();
// Same status semantics: a completed row is struck through, the
// in-progress row is not.
expect(screen.getByText(WRITE_TESTS).className).toContain("line-through");
expect(screen.getByText(IMPLEMENT_FEATURE).className).not.toContain("line-through");
});
it("shows an empty state when the active session has no todo entries anywhere", () => {
mockAppState.sessionTodos.bySessionId = {};
mockMessages.items = [];
render(<>{renderPanel("todos-panel", "todos", {})}</>);
expect(screen.getByTestId("todos-panel-empty-state").textContent).toBe("No todos yet.");
});
it("falls back to the latest persisted todo message when the live store has no entry for the session (a completed/reopened session)", () => {
mockAppState.sessionTodos.bySessionId = {};
mockMessages.items = [
{
id: "m1",
type: "todo",
turn_id: "turn-1",
metadata: {
todos: [
{ text: WRITE_TESTS, done: true, status: "completed" },
{ text: IMPLEMENT_FEATURE, done: false, status: "in_progress" },
],
},
},
];
render(<>{renderPanel("todos-panel", "todos", {})}</>);
const rows = screen.getAllByText(new RegExp(`${WRITE_TESTS}|${IMPLEMENT_FEATURE}`));
expect(rows.map((row) => row.textContent)).toEqual([WRITE_TESTS, IMPLEMENT_FEATURE]);
expect(screen.getByText("1/2 completed")).toBeTruthy();
expect(screen.queryByTestId("todos-panel-empty-state")).toBeNull();
});
});