mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
test(ui): cover turn diff and toolbar boundaries
This commit is contained in:
parent
eb6d4a3c2d
commit
d1337a6c85
3 changed files with 264 additions and 2 deletions
|
|
@ -42,6 +42,50 @@ describe("createToolbarPanelActions", () => {
|
|||
expect(stateStore.getState().ui.toolbarPanel).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps the panel open during rename while clearing archive confirmation on an outside pointer", () => {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
const actions = createToolbarPanelActions({
|
||||
stateStore,
|
||||
threadActions: { archiveThread: vi.fn() } as unknown as ThreadManagementActions,
|
||||
});
|
||||
actions.toggleHistory();
|
||||
actions.startArchive("thread");
|
||||
|
||||
actions.closeOnOutsidePointer({
|
||||
hit: { insideToolbarPanel: false, insideArchiveConfirm: false },
|
||||
renameEditing: true,
|
||||
});
|
||||
|
||||
expect(stateStore.getState().ui.toolbarPanel).toBe("history");
|
||||
expect(actions.archiveConfirmId()).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps archive confirmation only for pointers inside its confirmation row", () => {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
const actions = createToolbarPanelActions({
|
||||
stateStore,
|
||||
threadActions: { archiveThread: vi.fn() } as unknown as ThreadManagementActions,
|
||||
});
|
||||
actions.toggleHistory();
|
||||
actions.startArchive("thread");
|
||||
|
||||
actions.closeOnOutsidePointer({
|
||||
hit: { insideToolbarPanel: true, insideArchiveConfirm: true },
|
||||
renameEditing: false,
|
||||
});
|
||||
|
||||
expect(stateStore.getState().ui.toolbarPanel).toBe("history");
|
||||
expect(actions.archiveConfirmId()).toBe("thread");
|
||||
|
||||
actions.closeOnOutsidePointer({
|
||||
hit: { insideToolbarPanel: true, insideArchiveConfirm: false },
|
||||
renameEditing: false,
|
||||
});
|
||||
|
||||
expect(stateStore.getState().ui.toolbarPanel).toBe("history");
|
||||
expect(actions.archiveConfirmId()).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps the Set goal action reachable while a restored thread needs hydration", () => {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
stateStore.dispatch({ type: "panel/restored-thread-applied", threadId: "restored", fallbackTitle: "Restored" });
|
||||
|
|
@ -63,4 +107,39 @@ describe("createToolbarPanelActions", () => {
|
|||
|
||||
expect(startEditingCurrent).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("gates side chat, compaction, and goal mutation at invocation time", () => {
|
||||
let enabled = false;
|
||||
const openSideChat = vi.fn();
|
||||
const compactActiveThread = vi.fn().mockResolvedValue(undefined);
|
||||
const startEditingCurrent = vi.fn();
|
||||
const actions = createToolbarUiActions({
|
||||
connectionActions: {} as never,
|
||||
reconnectPanel: vi.fn(),
|
||||
threadActions: { compactActiveThread } as never,
|
||||
goals: { startEditingCurrent } as never,
|
||||
toolbarPanel: {} as never,
|
||||
rename: {} as never,
|
||||
navigation: {} as never,
|
||||
openSideChat,
|
||||
canStartSideChat: () => enabled,
|
||||
canCompact: () => enabled,
|
||||
canMutateGoal: () => enabled,
|
||||
});
|
||||
|
||||
actions.chat.startSideChat?.();
|
||||
actions.chat.compactContext();
|
||||
actions.chat.setGoal();
|
||||
expect(openSideChat).not.toHaveBeenCalled();
|
||||
expect(compactActiveThread).not.toHaveBeenCalled();
|
||||
expect(startEditingCurrent).not.toHaveBeenCalled();
|
||||
|
||||
enabled = true;
|
||||
actions.chat.startSideChat?.();
|
||||
actions.chat.compactContext();
|
||||
actions.chat.setGoal();
|
||||
expect(openSideChat).toHaveBeenCalledOnce();
|
||||
expect(compactActiveThread).toHaveBeenCalledOnce();
|
||||
expect(startEditingCurrent).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
70
tests/features/chat/panel/toolbar-hit-test.test.ts
Normal file
70
tests/features/chat/panel/toolbar-hit-test.test.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// @vitest-environment jsdom
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { toolbarOutsidePointerHit } from "../../../../src/features/chat/panel/toolbar-hit-test.dom";
|
||||
|
||||
function pointerAt(target: EventTarget | null): PointerEvent {
|
||||
return { target } as PointerEvent;
|
||||
}
|
||||
|
||||
describe("toolbarOutsidePointerHit", () => {
|
||||
it("classifies toolbar panels and archive confirmation descendants inside the panel root", () => {
|
||||
const root = document.createElement("div");
|
||||
const panel = root.appendChild(document.createElement("div"));
|
||||
panel.className = "codex-panel__toolbar-panel";
|
||||
const ordinaryTarget = panel.appendChild(document.createElement("button"));
|
||||
const archiveConfirm = panel.appendChild(document.createElement("div"));
|
||||
archiveConfirm.className = "codex-panel__archive-confirm";
|
||||
const confirmTarget = archiveConfirm.appendChild(document.createElement("button"));
|
||||
|
||||
expect(toolbarOutsidePointerHit(pointerAt(ordinaryTarget), root, window)).toEqual({
|
||||
insideToolbarPanel: true,
|
||||
insideArchiveConfirm: false,
|
||||
});
|
||||
expect(toolbarOutsidePointerHit(pointerAt(confirmTarget), root, window)).toEqual({
|
||||
insideToolbarPanel: true,
|
||||
insideArchiveConfirm: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not treat a matching toolbar class outside the supplied root as inside", () => {
|
||||
const root = document.createElement("div");
|
||||
const externalPanel = document.createElement("div");
|
||||
externalPanel.className = "codex-panel__toolbar-primary";
|
||||
const target = externalPanel.appendChild(document.createElement("button"));
|
||||
|
||||
expect(toolbarOutsidePointerHit(pointerAt(target), root, window)).toEqual({
|
||||
insideToolbarPanel: false,
|
||||
insideArchiveConfirm: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the view window when deciding whether the event target is a DOM element", () => {
|
||||
const root = document.createElement("div");
|
||||
const panel = root.appendChild(document.createElement("div"));
|
||||
panel.className = "codex-panel__toolbar-panel";
|
||||
const target = panel.appendChild(document.createElement("button"));
|
||||
const iframe = document.body.appendChild(document.createElement("iframe"));
|
||||
const foreignWindow = iframe.contentWindow;
|
||||
|
||||
expect(foreignWindow).not.toBeNull();
|
||||
expect(toolbarOutsidePointerHit(pointerAt(target), root, foreignWindow)).toEqual({
|
||||
insideToolbarPanel: false,
|
||||
insideArchiveConfirm: false,
|
||||
});
|
||||
|
||||
iframe.remove();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ name: "a missing root", root: null, viewWindow: window, target: document.body },
|
||||
{ name: "a missing view window", root: document.createElement("div"), viewWindow: null, target: document.body },
|
||||
{ name: "a non-element target", root: document.createElement("div"), viewWindow: window, target: document },
|
||||
])("classifies $name as outside", ({ root, viewWindow, target }) => {
|
||||
expect(toolbarOutsidePointerHit(pointerAt(target), root, viewWindow)).toEqual({
|
||||
insideToolbarPanel: false,
|
||||
insideArchiveConfirm: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
// @vitest-environment jsdom
|
||||
|
||||
import type { WorkspaceLeaf } from "obsidian";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { persistedTurnDiffViewState } from "../../../src/features/turn-diff/model";
|
||||
import { isPersistedTurnDiffViewState, persistedTurnDiffViewState } from "../../../src/features/turn-diff/model";
|
||||
import { renderTurnDiffView } from "../../../src/features/turn-diff/render.dom";
|
||||
import { CodexTurnDiffView } from "../../../src/features/turn-diff/view.obsidian";
|
||||
import { installObsidianDomShims } from "../../support/dom";
|
||||
|
|
@ -11,6 +11,10 @@ import { installObsidianDomShims } from "../../support/dom";
|
|||
installObsidianDomShims();
|
||||
|
||||
describe("turn diff view decisions", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("renders the turn diff view with classified unified diff lines", () => {
|
||||
const parent = document.createElement("div");
|
||||
const copyDiff = vi.fn();
|
||||
|
|
@ -145,6 +149,42 @@ describe("turn diff view decisions", () => {
|
|||
expect(persisted).not.toHaveProperty("diff");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "a complete state with a vault path",
|
||||
value: { threadId: "thread", turnId: "turn", cwd: "/vault/project", files: ["src/main.ts"] },
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "a complete state without a working directory",
|
||||
value: { threadId: "thread", turnId: "turn", cwd: null, files: [] },
|
||||
valid: true,
|
||||
},
|
||||
{ name: "null", value: null, valid: false },
|
||||
{
|
||||
name: "a missing turn id",
|
||||
value: { threadId: "thread", cwd: "/vault/project", files: ["src/main.ts"] },
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "a non-array file list",
|
||||
value: { threadId: "thread", turnId: "turn", cwd: "/vault/project", files: "src/main.ts" },
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "a file list containing a non-string value",
|
||||
value: { threadId: "thread", turnId: "turn", cwd: "/vault/project", files: ["src/main.ts", 42] },
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "an undefined working directory",
|
||||
value: { threadId: "thread", turnId: "turn", files: [] },
|
||||
valid: false,
|
||||
},
|
||||
])("classifies $name as persisted state: $valid", ({ value, valid }) => {
|
||||
expect(isPersistedTurnDiffViewState(value)).toBe(valid);
|
||||
});
|
||||
|
||||
it("renders restored turn diff metadata without unavailable diff text", () => {
|
||||
const parent = document.createElement("div");
|
||||
|
||||
|
|
@ -156,6 +196,79 @@ describe("turn diff view decisions", () => {
|
|||
expect(parent.querySelector(".codex-panel-turn-diff__diff")).toBeNull();
|
||||
});
|
||||
|
||||
it("restores only persisted metadata and clears an in-memory diff payload", async () => {
|
||||
const containerEl = document.createElement("div");
|
||||
const view = new CodexTurnDiffView({ containerEl } as unknown as WorkspaceLeaf);
|
||||
view.setDiffPayload({
|
||||
threadId: "live-thread",
|
||||
turnId: "live-turn",
|
||||
cwd: "/vault/project",
|
||||
files: ["src/live.ts"],
|
||||
diff: "@@\n-old\n+new",
|
||||
});
|
||||
|
||||
await view.setState(
|
||||
{
|
||||
threadId: "restored-thread",
|
||||
turnId: "restored-turn",
|
||||
cwd: null,
|
||||
files: ["src/restored.ts"],
|
||||
},
|
||||
{} as never,
|
||||
);
|
||||
|
||||
expect(view.getState()).toEqual({
|
||||
threadId: "restored-thread",
|
||||
turnId: "restored-turn",
|
||||
cwd: null,
|
||||
files: ["src/restored.ts"],
|
||||
});
|
||||
expect(view.contentEl.textContent).toContain("Turn diff is no longer available.");
|
||||
expect(view.contentEl.textContent).not.toContain("old");
|
||||
expect(view.contentEl.querySelector(".codex-panel-turn-diff__copy")).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects invalid restored metadata instead of showing a partial turn identity", async () => {
|
||||
const containerEl = document.createElement("div");
|
||||
const view = new CodexTurnDiffView({ containerEl } as unknown as WorkspaceLeaf);
|
||||
|
||||
await view.setState(
|
||||
{
|
||||
threadId: "thread",
|
||||
turnId: "turn",
|
||||
cwd: "/vault/project",
|
||||
files: ["src/main.ts", 42],
|
||||
},
|
||||
{} as never,
|
||||
);
|
||||
|
||||
expect(view.getState()).toEqual({});
|
||||
expect(view.contentEl.textContent).toBe("No turn diff selected.");
|
||||
});
|
||||
|
||||
it("copies the current in-memory diff from the view action", async () => {
|
||||
const writeText = vi.fn().mockResolvedValue(undefined);
|
||||
vi.stubGlobal("navigator", {
|
||||
...navigator,
|
||||
clipboard: { writeText },
|
||||
});
|
||||
const containerEl = document.createElement("div");
|
||||
const view = new CodexTurnDiffView({ containerEl } as unknown as WorkspaceLeaf);
|
||||
view.setDiffPayload({
|
||||
threadId: "thread",
|
||||
turnId: "turn",
|
||||
cwd: "/vault/project",
|
||||
files: ["src/main.ts"],
|
||||
diff: "@@\n-old\n+new",
|
||||
});
|
||||
|
||||
view.contentEl.querySelector<HTMLButtonElement>(".codex-panel-turn-diff__copy")?.click();
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(writeText).toHaveBeenCalledWith("@@\n-old\n+new");
|
||||
});
|
||||
});
|
||||
|
||||
it("unmounts the turn diff Preact root when the view closes", async () => {
|
||||
const containerEl = document.createElement("div");
|
||||
const view = new CodexTurnDiffView({ containerEl } as unknown as WorkspaceLeaf);
|
||||
|
|
|
|||
Loading…
Reference in a new issue