mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Add archive and fork action regression tests
This commit is contained in:
parent
78e9be0ed1
commit
82f3e61c10
2 changed files with 111 additions and 1 deletions
107
tests/features/chat/thread-actions.test.ts
Normal file
107
tests/features/chat/thread-actions.test.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { AppServerClient } from "../../../src/app-server/client";
|
||||
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import { ChatThreadActionController, type ChatThreadActionControllerHost } from "../../../src/features/chat/thread-actions";
|
||||
import type { DisplayItem } from "../../../src/features/chat/display/types";
|
||||
import { DEFAULT_SETTINGS } from "../../../src/settings/model";
|
||||
|
||||
describe("ChatThreadActionController", () => {
|
||||
it("forks from a selected turn by dropping later turns on the fork", async () => {
|
||||
const client = clientMock();
|
||||
const host = hostMock({ client, displayItems: turnItems() });
|
||||
const controller = new ChatThreadActionController(host);
|
||||
|
||||
await controller.forkThreadFromTurn("source", "turn-1", false);
|
||||
|
||||
expect(client.forkThread).toHaveBeenCalledWith("source", "/vault");
|
||||
expect(client.rollbackThread).toHaveBeenCalledWith("forked", 2);
|
||||
expect(host.openThreadInNewView).toHaveBeenCalledWith("forked");
|
||||
expect(client.archiveThread).not.toHaveBeenCalled();
|
||||
expect(host.closePanel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not archive the source when fork and archive cannot open the fork panel", async () => {
|
||||
const client = clientMock();
|
||||
const host = hostMock({ client, displayItems: turnItems() });
|
||||
host.openThreadInNewView.mockRejectedValue(new Error("no panel"));
|
||||
const controller = new ChatThreadActionController(host);
|
||||
|
||||
await controller.forkThreadFromTurn("source", "turn-2", true);
|
||||
|
||||
expect(client.forkThread).toHaveBeenCalledWith("source", "/vault");
|
||||
expect(client.rollbackThread).toHaveBeenCalledWith("forked", 1);
|
||||
expect(client.archiveThread).not.toHaveBeenCalled();
|
||||
expect(host.closePanel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not close the source panel when fork and archive fails to archive", async () => {
|
||||
const client = clientMock();
|
||||
client.archiveThread.mockRejectedValue(new Error("archive failed"));
|
||||
const host = hostMock({ client, displayItems: turnItems() });
|
||||
const controller = new ChatThreadActionController(host);
|
||||
|
||||
await controller.forkThreadFromTurn("source", "turn-3", true);
|
||||
|
||||
expect(client.rollbackThread).not.toHaveBeenCalled();
|
||||
expect(client.archiveThread).toHaveBeenCalledWith("source");
|
||||
expect(host.closePanel).not.toHaveBeenCalled();
|
||||
expect(host.addSystemMessage).toHaveBeenCalledWith("archive failed");
|
||||
});
|
||||
|
||||
it("closes the source panel after fork and archive succeeds", async () => {
|
||||
const client = clientMock();
|
||||
const host = hostMock({ client, displayItems: turnItems() });
|
||||
const controller = new ChatThreadActionController(host);
|
||||
|
||||
await controller.forkThreadFromTurn("source", "turn-3", true);
|
||||
|
||||
expect(client.archiveThread).toHaveBeenCalledWith("source");
|
||||
expect(host.closePanel).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
function turnItems(): DisplayItem[] {
|
||||
return [
|
||||
{ id: "u1", kind: "message", role: "user", text: "one", turnId: "turn-1", markdown: true },
|
||||
{ id: "a1", kind: "message", role: "assistant", text: "one answer", turnId: "turn-1", markdown: true },
|
||||
{ id: "u2", kind: "message", role: "user", text: "two", turnId: "turn-2", markdown: true },
|
||||
{ id: "a2", kind: "message", role: "assistant", text: "two answer", turnId: "turn-2", markdown: true },
|
||||
{ id: "u3", kind: "message", role: "user", text: "three", turnId: "turn-3", markdown: true },
|
||||
{ id: "a3", kind: "message", role: "assistant", text: "three answer", turnId: "turn-3", markdown: true },
|
||||
];
|
||||
}
|
||||
|
||||
function clientMock() {
|
||||
return {
|
||||
forkThread: vi.fn().mockResolvedValue({ thread: { id: "forked" } }),
|
||||
rollbackThread: vi.fn().mockResolvedValue({ thread: { id: "forked" } }),
|
||||
archiveThread: vi.fn().mockResolvedValue({}),
|
||||
readThread: vi.fn(),
|
||||
setThreadName: vi.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
function hostMock({ client, displayItems }: { client: ReturnType<typeof clientMock>; displayItems: DisplayItem[] }) {
|
||||
const state = createChatState();
|
||||
const stateStore = createChatStateStore({ ...state, displayItems });
|
||||
return {
|
||||
stateStore,
|
||||
vaultPath: "/vault",
|
||||
settings: () => DEFAULT_SETTINGS,
|
||||
archiveAdapter: vi.fn(),
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
currentClient: () => client as unknown as AppServerClient,
|
||||
history: {} as never,
|
||||
addSystemMessage: vi.fn(),
|
||||
setStatus: vi.fn(),
|
||||
setComposerText: vi.fn(),
|
||||
openThreadInNewView: vi.fn().mockResolvedValue(undefined),
|
||||
notifyThreadArchived: vi.fn(),
|
||||
notifyThreadRenamed: vi.fn(),
|
||||
notifyActiveThreadIdentityChanged: vi.fn(),
|
||||
refreshThreads: vi.fn().mockResolvedValue(undefined),
|
||||
refreshSharedThreadListFromOpenSurface: vi.fn(),
|
||||
closePanel: vi.fn(),
|
||||
} satisfies ChatThreadActionControllerHost;
|
||||
}
|
||||
|
|
@ -277,22 +277,25 @@ describe("CodexPanelPlugin boot restored panel loading", () => {
|
|||
|
||||
it("closes matching chat panels only when archive notification requests it", async () => {
|
||||
const { CodexChatView } = await import("../src/features/chat/view");
|
||||
const restoredMatchingLeaf = leaf({ state: { threadId: "thread-1", threadTitle: "Restored" } });
|
||||
const matchingLeaf = leaf();
|
||||
matchingLeaf.view = chatView(CodexChatView, matchingLeaf);
|
||||
vi.spyOn(matchingLeaf.view as CodexChatView, "openPanelSnapshot").mockReturnValue(panelSnapshot({ threadId: "thread-1" }));
|
||||
const otherLeaf = leaf();
|
||||
otherLeaf.view = chatView(CodexChatView, otherLeaf);
|
||||
vi.spyOn(otherLeaf.view as CodexChatView, "openPanelSnapshot").mockReturnValue(panelSnapshot({ threadId: "thread-2" }));
|
||||
const plugin = await pluginWithLeaves([matchingLeaf, otherLeaf]);
|
||||
const plugin = await pluginWithLeaves([restoredMatchingLeaf, matchingLeaf, otherLeaf]);
|
||||
vi.spyOn(plugin, "refreshSharedThreadListFromOpenSurface").mockImplementation(() => undefined);
|
||||
|
||||
plugin.notifyThreadArchived("thread-1");
|
||||
|
||||
expect(restoredMatchingLeaf.detach).not.toHaveBeenCalled();
|
||||
expect(matchingLeaf.detach).not.toHaveBeenCalled();
|
||||
expect(otherLeaf.detach).not.toHaveBeenCalled();
|
||||
|
||||
plugin.notifyThreadArchived("thread-1", { closeOpenPanels: true });
|
||||
|
||||
expect(restoredMatchingLeaf.detach).toHaveBeenCalledOnce();
|
||||
expect(matchingLeaf.detach).toHaveBeenCalledOnce();
|
||||
expect(otherLeaf.detach).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue