diff --git a/tests/features/chat/panel/composition.test.ts b/tests/features/chat/panel/composition.test.ts deleted file mode 100644 index 249588aa..00000000 --- a/tests/features/chat/panel/composition.test.ts +++ /dev/null @@ -1,163 +0,0 @@ -// @vitest-environment jsdom - -import { describe, expect, it, vi } from "vitest"; -import type { App, Component, EventRef } from "obsidian"; - -import type { RuntimeSnapshot } from "../../../../src/features/chat/runtime/effective-settings"; -import { createChatStateStore } from "../../../../src/features/chat/state/reducer"; -import { createChatViewControllers } from "../../../../src/features/chat/panel/composition"; -import type { ChatControllerCompositionPorts } from "../../../../src/features/chat/panel/controller-ports"; -import { ChatConnectionWorkTracker, ChatResumeWorkTracker, ChatViewDeferredTasks } from "../../../../src/features/chat/panel/lifecycle"; -import { ChatMessageScrollIntentController } from "../../../../src/features/chat/panel/message-scroll-intent-controller"; -import type { ComposerMetaViewModel } from "../../../../src/features/chat/panel/view-model"; -import { DEFAULT_SETTINGS } from "../../../../src/settings/model"; - -describe("createChatViewControllers", () => { - it("constructs the chat controller graph with direct shell nodes", () => { - const controllers = createChatViewControllers(createPorts()); - - expect(controllers.connection.controller).toBeTruthy(); - expect(controllers.inbound.controller).toBeTruthy(); - expect(controllers.thread.resume).toBeTruthy(); - expect(controllers.render.messages).toBeTruthy(); - expect(controllers.composer.controller).toBeTruthy(); - expect(controllers.render.controller).toBeTruthy(); - }); -}); - -function createPorts(): ChatControllerCompositionPorts { - const stateStore = createChatStateStore(); - const deferredTasks = new ChatViewDeferredTasks(() => window); - const root = document.createElement("div"); - - return { - obsidian: { - app: createApp(), - owner: createOwner(), - viewId: "test-view", - registerEvent: vi.fn(), - registerPointerDown: vi.fn(), - archiveAdapter: () => ({ - exists: vi.fn().mockResolvedValue(false), - mkdir: vi.fn().mockResolvedValue(undefined), - write: vi.fn().mockResolvedValue(undefined), - }), - }, - plugin: { - settings: DEFAULT_SETTINGS, - vaultPath: "/vault", - openThreadInNewView: vi.fn().mockResolvedValue(undefined), - focusThreadInOpenView: vi.fn().mockResolvedValue(false), - openTurnDiff: vi.fn().mockResolvedValue(undefined), - notifyThreadArchived: vi.fn(), - notifyThreadRenamed: vi.fn(), - refreshThreadsViewLiveState: vi.fn(), - refreshSharedThreadListFromOpenSurface: vi.fn(), - applyThreadListSnapshot: vi.fn(), - refreshThreadList: vi.fn(async (fetchThreads: () => Promise) => fetchThreads()), - cachedThreadList: () => null, - publishAppServerMetadata: vi.fn(), - cachedAppServerMetadata: () => null, - }, - state: { - stateStore, - getState: () => stateStore.getState(), - systemItem: (text) => ({ id: "system", kind: "system", role: "system", text }), - structuredSystemItem: (text, details) => ({ id: "system", kind: "system", role: "system", text, details }), - }, - client: { - getClient: () => null, - setClient: vi.fn(), - clear: vi.fn(), - }, - lifecycle: { - deferredTasks, - resumeWork: new ChatResumeWorkTracker(), - connectionWork: new ChatConnectionWorkTracker(), - messageScrollIntent: new ChatMessageScrollIntentController(), - getOpened: () => false, - setOpened: vi.fn(), - getClosing: () => false, - setClosing: vi.fn(), - invalidateConnectionWork: vi.fn(), - scheduleDeferredDiagnostics: vi.fn(), - clearDeferredDiagnostics: vi.fn(), - scheduleDeferredRestoredThreadHydration: vi.fn(), - clearDeferredRestoredThreadHydration: vi.fn(), - scheduleDeferredAppServerWarmup: vi.fn(), - }, - render: { - panelRoot: () => root, - toolbarNode: () => null, - goalNode: () => null, - messagesNode: () => null, - composerNode: () => null, - closeToolbarPanelOnOutsidePointer: vi.fn(), - schedule: vi.fn(), - }, - messages: { - pendingRequestsSignature: () => "", - }, - composerView: { - composerPlaceholder: () => "", - composerMetaViewModel: () => composerMeta(), - }, - runtime: { - runtimeSnapshot: () => ({}) as RuntimeSnapshot, - collaborationModeLabel: () => "Plan", - connectionDiagnosticDetails: () => [], - modelStatusLines: () => [], - effortStatusLines: () => [], - statusSummaryLines: () => [], - }, - thread: { - ensureRestoredThreadLoaded: vi.fn().mockResolvedValue(false), - startNewThread: vi.fn().mockResolvedValue(undefined), - loadSharedThreadList: vi.fn().mockResolvedValue(undefined), - notifyIdentityChanged: vi.fn(), - refreshTabHeader: vi.fn(), - }, - liveState: { - refresh: vi.fn(), - deferRefresh: vi.fn(), - }, - scroll: { - forceBottom: vi.fn(), - followBottom: vi.fn(), - preservePosition: vi.fn(), - }, - status: { - set: vi.fn(), - }, - }; -} - -function createApp(): App { - return { - vault: { - adapter: {}, - on: vi.fn(() => ({}) as EventRef), - }, - workspace: { - getActiveFile: () => null, - openLinkText: vi.fn(), - }, - } as unknown as App; -} - -function createOwner(): Component { - return {} as Component; -} - -function composerMeta(): ComposerMetaViewModel { - return { - fatal: null, - context: { cells: [], percent: "--%" }, - statusSummary: "", - model: "", - effort: null, - planActive: false, - autoReviewActive: false, - fastActive: false, - }; -} diff --git a/tests/features/chat/state-reducer.test.ts b/tests/features/chat/state-reducer.test.ts index ed987952..ac8f99b4 100644 --- a/tests/features/chat/state-reducer.test.ts +++ b/tests/features/chat/state-reducer.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; +import { describe, expect, it } from "vitest"; import { activeTurnId, @@ -173,34 +173,6 @@ describe("chatReducer", () => { expect(withHistoryPanel.ui.openDetails).toBe(withDiff.ui.openDetails); }); - it("returns the same state reference for no-op actions", () => { - const state = createChatState(); - - expect(chatReducer(state, { type: "connection/status-set", status: "Idle" })).toBe(state); - expect(chatReducer(state, { type: "request/approval-queued", approval: approval(1) })).not.toBe(state); - const queued = chatReducer(state, { type: "request/approval-queued", approval: approval(1) }); - expect(chatReducer(queued, { type: "request/approval-queued", approval: approval(1) })).toBe(queued); - }); - - it("keeps slice actions scoped to their owning state group", () => { - const connectionState = createChatState(); - expectOnlySliceReferenceChanged( - chatReducer(connectionState, { type: "connection/status-set", status: "Working" }), - connectionState, - "connection", - ); - - const uiState = createChatState(); - expectOnlySliceReferenceChanged(chatReducer(uiState, { type: "ui/panel-set", panel: "history" }), uiState, "ui"); - - const transcriptState = createChatState(); - expectOnlySliceReferenceChanged( - chatReducer(transcriptState, { type: "transcript/items-replaced", items: [message("next")] }), - transcriptState, - "transcript", - ); - }); - it("deduplicates reported logs while keeping reported log state immutable", () => { const state = createChatState(); const item = { id: "log", kind: "system", role: "system", text: "once" } satisfies DisplayItem; @@ -497,22 +469,6 @@ describe("chatReducer", () => { }); expect(panelB.getState().requests.userInputDrafts.get("2:note")).toBe("panel B answer"); }); - - it("notifies ChatStateStore subscribers only when the state reference changes", () => { - const store = createChatStateStore(); - const listener = vi.fn(); - const unsubscribe = store.subscribe(listener); - - store.dispatch({ type: "connection/status-set", status: "Idle" }); - expect(listener).not.toHaveBeenCalled(); - - store.dispatch({ type: "connection/status-set", status: "Working" }); - expect(listener).toHaveBeenCalledTimes(1); - - unsubscribe(); - store.dispatch({ type: "connection/status-set", status: "Done" }); - expect(listener).toHaveBeenCalledTimes(1); - }); }); function message(id: string): DisplayItem { @@ -523,17 +479,6 @@ function suggestion(display: string): ChatState["composer"]["suggestions"][numbe return { display, detail: "Plan mode", replacement: display, start: 0, appendSpaceOnInsert: true }; } -function expectOnlySliceReferenceChanged(next: ChatState, previous: ChatState, changedKey: keyof ChatState): void { - expect(next).not.toBe(previous); - for (const key of Object.keys(previous) as (keyof ChatState)[]) { - if (key === changedKey) { - expect(next[key]).not.toBe(previous[key]); - } else { - expect(next[key]).toBe(previous[key]); - } - } -} - function approval(requestId: number): ChatState["requests"]["approvals"][number] { return { requestId, diff --git a/tests/features/chat/threads/thread-actions.test.ts b/tests/features/chat/threads/thread-actions.test.ts index f78cf0e4..ee2dd12e 100644 --- a/tests/features/chat/threads/thread-actions.test.ts +++ b/tests/features/chat/threads/thread-actions.test.ts @@ -101,19 +101,6 @@ describe("createChatThreadActions", () => { expect(host.openThreadInCurrentPanel).not.toHaveBeenCalled(); }); - it("does not open the fork in a new panel before archiving the source", async () => { - const client = clientMock(); - const host = hostMock({ client, displayItems: turnItems() }); - const controller = createChatThreadActions(host); - - await controller.forkThreadFromTurn("source", "turn-2", true); - - expect(client.forkThread).toHaveBeenCalledWith("source", "/vault"); - expect(client.rollbackThread).toHaveBeenCalledWith("forked", 1); - expect(host.openThreadInNewView).not.toHaveBeenCalled(); - expect(client.archiveThread).toHaveBeenCalledWith("source"); - }); - it("saves the source before replacing the panel during fork and archive", async () => { const client = clientMock(); const adapter = archiveAdapterMock(); @@ -158,22 +145,6 @@ describe("createChatThreadActions", () => { expect(host.addSystemMessage).toHaveBeenCalledWith("archive failed"); }); - it("replaces the source panel before notifying surfaces after fork and archive succeeds", async () => { - const client = clientMock(); - const host = hostMock({ client, displayItems: turnItems() }); - const controller = createChatThreadActions(host); - - await controller.forkThreadFromTurn("source", "turn-3", true); - - expect(client.archiveThread).toHaveBeenCalledWith("source"); - expect(host.openThreadInCurrentPanel).toHaveBeenCalledWith("forked"); - expect(host.notifyThreadArchived).toHaveBeenCalledWith("source"); - const openOrder = host.openThreadInCurrentPanel.mock.invocationCallOrder[0]; - const notifyOrder = host.notifyThreadArchived.mock.invocationCallOrder[0]; - if (openOrder === undefined || notifyOrder === undefined) throw new Error("Expected open and archive notification calls."); - expect(openOrder).toBeLessThan(notifyOrder); - }); - it("notifies surfaces when fork and archive succeeds but the fork cannot replace the source panel", async () => { const client = clientMock(); const host = hostMock({ client, displayItems: turnItems() }); diff --git a/tests/features/chat/ui/message-stream/blocks-and-messages.test.tsx b/tests/features/chat/ui/message-stream/blocks-and-messages.test.tsx index bb39e8a2..bc3265b3 100644 --- a/tests/features/chat/ui/message-stream/blocks-and-messages.test.tsx +++ b/tests/features/chat/ui/message-stream/blocks-and-messages.test.tsx @@ -16,7 +16,6 @@ import { messageStreamBlocks, renderMessageBlockElement, renderMessageStreamBlocksInAct, - renderUiRootInAct, runningTurnLifecycle, startingTurnLifecycle, unmountUiRootInAct, @@ -93,58 +92,6 @@ describe("message stream rendering and message actions", () => { unmountUiRootInAct(parent); }); - it("renders the history bar as a Preact block", () => { - const loadOlderTurns = vi.fn(); - const [historyBlock] = messageStreamBlocks({ - activeThreadId: "thread", - turnLifecycle: idleTurnLifecycle(), - historyCursor: "cursor", - loadingHistory: false, - displayItems: [], - openDetails: new Set(), - loadOlderTurns, - renderMarkdown: (element, text) => element.createDiv({ text }), - }); - const parent = document.createElement("div"); - - expect(historyBlock.key).toBe("history-bar"); - expect(historyBlock.node).not.toBeUndefined(); - renderUiRootInAct(parent, historyBlock.node); - - const button = expectPresent(parent.querySelector("button")); - expect(parent.querySelector(".codex-panel__history-bar")).not.toBeNull(); - expect(button.textContent).toBe("Load older"); - expect(button.disabled).toBe(false); - - button.click(); - - expect(loadOlderTurns).toHaveBeenCalledOnce(); - unmountUiRootInAct(parent); - }); - - it("renders the empty message stream state as a Preact block", () => { - const [emptyBlock] = messageStreamBlocks({ - activeThreadId: null, - turnLifecycle: idleTurnLifecycle(), - historyCursor: null, - loadingHistory: false, - displayItems: [], - openDetails: new Set(), - loadOlderTurns: vi.fn(), - renderMarkdown: (element, text) => element.createDiv({ text }), - }); - const parent = document.createElement("div"); - - expect(emptyBlock.key).toBe("empty"); - expect(emptyBlock.node).not.toBeUndefined(); - renderUiRootInAct(parent, emptyBlock.node); - - const empty = expectPresent(parent.querySelector(".codex-panel__message--system")); - expect(empty.classList.contains("codex-panel__message")).toBe(true); - expect(empty.textContent).toBe("Send a message to start a conversation."); - unmountUiRootInAct(parent); - }); - it("renders review result items as compact auto-review tool rows", () => { const block = messageStreamBlocks({ activeThreadId: "thread", @@ -212,89 +159,6 @@ describe("message stream rendering and message actions", () => { expect([...element.querySelectorAll(".codex-panel__output-title")].map((title) => title.textContent)).toEqual([]); }); - it("keeps tool result Preact details mounted in the message stream host", () => { - const parent = document.createElement("div"); - const onDetailsToggle = vi.fn(); - - renderMessageStreamBlocksInAct( - parent, - messageStreamBlocks({ - activeThreadId: "thread", - turnLifecycle: idleTurnLifecycle(), - historyCursor: null, - loadingHistory: false, - displayItems: [ - { - id: "cmd-1", - kind: "command", - role: "tool", - text: "npm test", - command: "npm test", - cwd: "/vault", - status: "completed", - output: "ok", - executionState: "completed", - }, - ], - openDetails: new Set(), - onDetailsToggle, - loadOlderTurns: vi.fn(), - renderMarkdown: (element, text) => element.createDiv({ text }), - }), - ); - - const block = expectPresent(parent.querySelector('[data-codex-panel-block-key="item:cmd-1"]')); - const result = expectPresent(block.querySelector(".codex-panel__tool-result")); - expect(result.classList.contains("codex-panel__execution--completed")).toBe(true); - expect(result.querySelector(".codex-panel__tool-result-header")?.textContent).toBe("command"); - expect(result.querySelector(":scope > .codex-panel__tool-summary")?.textContent).toBe("npm test"); - expect(result.querySelector(".codex-panel__tool-summary")?.textContent).toBe("npm test"); - expect(result.querySelector(".codex-panel__meta-grid")?.textContent).toContain("commandnpm test"); - expect(result.querySelector(".codex-panel__output-title")?.textContent).toBe("Output"); - - const details = expectPresent(result.querySelector("details")); - void act(() => { - details.open = true; - details.dispatchEvent(new Event("toggle", { bubbles: false })); - }); - - expect(onDetailsToggle).toHaveBeenCalledWith("cmd-1:command-details", true); - unmountUiRootInAct(parent); - }); - - it("renders file change diffs through the Preact tool result adapter", () => { - const parent = document.createElement("div"); - - renderMessageStreamBlocksInAct( - parent, - messageStreamBlocks({ - activeThreadId: "thread", - turnLifecycle: idleTurnLifecycle(), - historyCursor: null, - loadingHistory: false, - workspaceRoot: "/vault", - displayItems: [ - { - id: "file-1", - kind: "fileChange", - role: "tool", - text: "Changed 1 file", - status: "completed", - changes: [{ kind: "modified", path: "/vault/src/app.ts", diff: "-old\n+new" }], - }, - ], - openDetails: new Set(["file-1:file-change-details"]), - loadOlderTurns: vi.fn(), - renderMarkdown: (element, text) => element.createDiv({ text }), - }), - ); - - expect(parent.querySelector(".codex-panel__tool-summary")?.textContent).toBe("src/app.ts"); - expect(parent.querySelector(".codex-panel-diff-file .codex-panel__output-title")?.textContent).toBe("modified src/app.ts"); - expect([...parent.querySelectorAll(".codex-panel-diff__line")].map((line) => line.textContent)).toEqual(["old", "new"]); - unmountUiRootInAct(parent); - }); - it("renders structured system result details as visible selectable meta rows", () => { const renderMarkdown = vi.fn((parent: HTMLElement, text: string) => parent.createDiv({ text: `markdown:${text}` })); const block = messageStreamBlocks({ @@ -538,83 +402,6 @@ describe("message stream rendering and message actions", () => { expect(onForkItem).toHaveBeenCalledWith(expect.objectContaining({ id: "a1" }), true); }); - it("keeps message Preact actions mounted in the message stream host", () => { - const parent = document.createElement("div"); - const copyText = vi.fn(); - const onImplementPlanItem = vi.fn(); - - renderMessageStreamBlocksInAct( - parent, - messageStreamBlocks({ - activeThreadId: "thread", - turnLifecycle: idleTurnLifecycle(), - historyCursor: null, - loadingHistory: false, - displayItems: [ - { - id: "p1", - kind: "message", - role: "assistant", - text: "Plan", - copyText: "Plan", - turnId: "turn-1", - messageKind: "proposedPlan", - messageState: "streaming", - }, - ], - openDetails: new Set(), - loadOlderTurns: vi.fn(), - renderMarkdown: (element, text) => element.createDiv({ text }), - copyText, - canImplementPlanItem: () => true, - onImplementPlanItem, - }), - ); - - parent.querySelector(".codex-panel__copy-message")?.click(); - parent.querySelector(".codex-panel__implement-plan")?.click(); - - expect(copyText).toHaveBeenCalledWith("Plan"); - expect(onImplementPlanItem).toHaveBeenCalledWith(expect.objectContaining({ id: "p1" })); - expect(parent.querySelector('[data-codex-panel-block-key="item:p1"] .codex-panel__message--assistant')).not.toBeNull(); - unmountUiRootInAct(parent); - }); - - it("renders message markdown through the Preact content adapter", () => { - const parent = document.createElement("div"); - const renderMarkdown = vi.fn((element: HTMLElement, text: string) => { - element.createDiv({ text: `rendered:${text}` }); - }); - - renderMessageStreamBlocksInAct( - parent, - messageStreamBlocks({ - activeThreadId: "thread", - turnLifecycle: idleTurnLifecycle(), - historyCursor: null, - loadingHistory: false, - displayItems: [ - { - id: "a1", - kind: "message", - role: "assistant", - text: "**answer**", - turnId: "turn-1", - messageKind: "assistantResponse", - messageState: "completed", - }, - ], - openDetails: new Set(), - loadOlderTurns: vi.fn(), - renderMarkdown, - }), - ); - - expect(renderMarkdown).toHaveBeenCalledWith(expect.any(HTMLElement), "**answer**"); - expect(parent.querySelector(".codex-panel__message-content")?.textContent).toBe("rendered:**answer**"); - unmountUiRootInAct(parent); - }); - it("updates message content when a streaming plan delta completes", () => { const parent = document.createElement("div"); const baseContext = { diff --git a/tests/features/chat/ui/message-stream/pending-requests.test.tsx b/tests/features/chat/ui/message-stream/pending-requests.test.tsx index 9939d9be..60a3b16f 100644 --- a/tests/features/chat/ui/message-stream/pending-requests.test.tsx +++ b/tests/features/chat/ui/message-stream/pending-requests.test.tsx @@ -561,40 +561,6 @@ describe("pending request renderer decisions", () => { expect(consumeAutoFocus).not.toHaveBeenCalled(); }); - it("keeps pending request Preact events mounted in the message stream host", () => { - const parent = document.createElement("div"); - const approval = pendingApproval(); - const resolveApproval = vi.fn(); - - renderMessageStreamBlocksInAct( - parent, - messageStreamBlocks({ - activeThreadId: "thread", - turnLifecycle: idleTurnLifecycle(), - historyCursor: null, - loadingHistory: false, - displayItems: [ - { id: "a1", kind: "message", role: "assistant", text: "Waiting", messageKind: "assistantResponse", messageState: "completed" }, - ], - openDetails: new Set(), - loadOlderTurns: vi.fn(), - renderMarkdown: (element, text) => element.createDiv({ text }), - pendingRequests: pendingRequestContext({ - signature: "approval:1", - snapshot: emptyPendingRequestSnapshot({ approvals: [approval] }), - actions: pendingRequestActions({ resolveApproval }), - }), - }), - ); - - actEvent(() => { - parent.querySelector(".codex-panel__pending-request-button")?.click(); - }); - - expect(resolveApproval).toHaveBeenCalledWith(approval, "accept"); - unmountUiRootInAct(parent); - }); - it("removes pending request blocks when the signature clears", () => { const parent = document.createElement("div"); const baseContext = { diff --git a/tests/features/chat/ui/message-stream/test-helpers.tsx b/tests/features/chat/ui/message-stream/test-helpers.tsx index f9ffe0d5..ddfa1a52 100644 --- a/tests/features/chat/ui/message-stream/test-helpers.tsx +++ b/tests/features/chat/ui/message-stream/test-helpers.tsx @@ -83,7 +83,7 @@ export function installMessageViewportMetrics( }) as typeof element.scrollTo; } -export function renderUiRootInAct(parent: HTMLElement, node: UiNode): void { +function renderUiRootInAct(parent: HTMLElement, node: UiNode): void { void act(() => { renderUiRoot(parent, node); }); diff --git a/tests/features/chat/ui/message-stream/work-log.test.tsx b/tests/features/chat/ui/message-stream/work-log.test.tsx index 83221328..7a54dec7 100644 --- a/tests/features/chat/ui/message-stream/work-log.test.tsx +++ b/tests/features/chat/ui/message-stream/work-log.test.tsx @@ -1,7 +1,6 @@ // @vitest-environment jsdom import { describe, expect, it, vi } from "vitest"; -import { act } from "preact/test-utils"; import type { DisplayItem } from "../../../../../src/features/chat/display/types"; import { topLevelDetailsSummaries } from "../../../../support/dom"; @@ -396,87 +395,6 @@ describe("work log renderer decisions", () => { expect(element.querySelector(".codex-panel__output pre")?.textContent).toBe("feedback: ok"); }); - it("keeps completed-turn activity group items mounted through Preact", () => { - const parent = document.createElement("div"); - const onDetailsToggle = vi.fn(); - - renderMessageStreamBlocksInAct( - parent, - messageStreamBlocks({ - activeThreadId: "thread", - turnLifecycle: idleTurnLifecycle(), - historyCursor: null, - loadingHistory: false, - displayItems: [ - { id: "u1", kind: "message", messageKind: "user", role: "user", text: "do it", turnId: "turn" }, - { - id: "hook-1", - kind: "hook", - role: "tool", - text: "postToolUse: Formatted 1 file.", - toolLabel: "hook", - turnId: "turn", - status: "completed", - details: [ - { - rows: [ - { key: "status", value: "completed" }, - { key: "event", value: "postToolUse" }, - ], - }, - { title: "Hook output", body: "feedback: ok" }, - ], - }, - { - id: "agent-1", - kind: "agent", - role: "tool", - text: "Spawn agent", - turnId: "turn", - tool: "spawnAgent", - status: "completed", - senderThreadId: "parent", - receiverThreadIds: ["child"], - prompt: null, - model: null, - reasoningEffort: null, - agents: [{ threadId: "child", status: "completed", message: "Done" }], - }, - { - id: "a1", - kind: "message", - role: "assistant", - text: "done", - turnId: "turn", - messageKind: "assistantResponse", - messageState: "completed", - }, - ], - openDetails: new Set(["turn:turn:activity", "hook-1:details"]), - onDetailsToggle, - loadOlderTurns: vi.fn(), - renderMarkdown: (element, text) => element.createDiv({ text }), - }), - ); - - const group = expectPresent(parent.querySelector('[data-codex-panel-block-key="activity:turn-turn-activity"]')); - expect(group.querySelector(":scope > .codex-panel__activity-group > summary")?.textContent).toBe("Work details: agent, hook"); - expect(group.querySelector(".codex-panel__tool-result .codex-panel__tool-result-header")?.textContent).toBe("hook"); - expect(group.querySelector(".codex-panel__tool-result > .codex-panel__tool-summary")?.textContent).toBe( - "postToolUse: Formatted 1 file.", - ); - expect(group.querySelector(".codex-panel__agent-activity .codex-panel__tool-summary")?.textContent).toBe("spawn child (completed)"); - - const details = expectPresent(group.querySelector(".codex-panel__activity-group")); - void act(() => { - details.open = false; - details.dispatchEvent(new Event("toggle", { bubbles: false })); - }); - - expect(onDetailsToggle).toHaveBeenCalledWith("turn:turn:activity", false); - unmountUiRootInAct(parent); - }); - it("renders task progress items as a dedicated task list", () => { const block = messageStreamBlocks({ activeThreadId: "thread", @@ -512,44 +430,6 @@ describe("work log renderer decisions", () => { expect(element.textContent).toContain("[>]Patch UI"); }); - it("keeps task progress Preact items mounted in the message stream host", () => { - const parent = document.createElement("div"); - - renderMessageStreamBlocksInAct( - parent, - messageStreamBlocks({ - activeThreadId: "thread", - turnLifecycle: runningTurnLifecycle("turn"), - historyCursor: null, - loadingHistory: false, - displayItems: [ - { - id: "plan-progress-turn", - kind: "taskProgress", - role: "tool", - text: "Plan\n[>] Patch UI", - turnId: "turn", - explanation: "Plan", - steps: [ - { step: "Inspect code", status: "completed" }, - { step: "Patch UI", status: "inProgress" }, - ], - status: "inProgress", - }, - ], - openDetails: new Set(), - loadOlderTurns: vi.fn(), - renderMarkdown: (element, text) => element.createDiv({ text }), - }), - ); - - const block = expectPresent(parent.querySelector('[data-codex-panel-block-key="live-task:plan-progress-turn"]')); - expect(block.querySelector(".codex-panel__task-progress .codex-panel__message-role")?.textContent).toBe("tasks"); - expect(block.textContent).toContain("[x]Inspect code"); - expect(block.textContent).toContain("[>]Patch UI"); - unmountUiRootInAct(parent); - }); - it("renders active task progress with the shared bottom live blocks", () => { const blocks = messageStreamBlocks({ activeThreadId: "thread", @@ -771,57 +651,6 @@ describe("work log renderer decisions", () => { expect(element.textContent).toContain("childcompleted: Done"); }); - it("keeps agent Preact details mounted in the message stream host", () => { - const parent = document.createElement("div"); - const onDetailsToggle = vi.fn(); - - renderMessageStreamBlocksInAct( - parent, - messageStreamBlocks({ - activeThreadId: "thread", - turnLifecycle: runningTurnLifecycle("turn"), - historyCursor: null, - loadingHistory: false, - displayItems: [ - { - id: "agent-1", - kind: "agent", - role: "tool", - text: "Spawn agent", - turnId: "turn", - tool: "spawnAgent", - status: "completed", - senderThreadId: "parent", - receiverThreadIds: ["child"], - prompt: "Inspect the renderer.", - model: "gpt-5.5", - reasoningEffort: "high", - agents: [{ threadId: "child", status: "completed", message: "Done" }], - }, - ], - openDetails: new Set(), - onDetailsToggle, - loadOlderTurns: vi.fn(), - renderMarkdown: (element, text) => element.createDiv({ text }), - }), - ); - - const block = expectPresent(parent.querySelector('[data-codex-panel-block-key="item:agent-1"]')); - expect(block.querySelector(".codex-panel__agent-activity .codex-panel__message-role")?.textContent).toBe("agent"); - expect(block.querySelector(".codex-panel__tool-summary")?.textContent).toBe("spawn child: Inspect the renderer. (completed)"); - expect([...block.querySelectorAll("details summary")].map((summary) => summary.textContent)).toEqual(["Details"]); - - const details = expectPresent(block.querySelector("details")); - void act(() => { - details.open = true; - details.dispatchEvent(new Event("toggle", { bubbles: false })); - }); - - expect(onDetailsToggle).toHaveBeenCalledWith("agent-1:agent-details", true); - expect(expectPresent(block.querySelector(".codex-panel__agent-activity")).classList.contains("is-open")).toBe(true); - unmountUiRootInAct(parent); - }); - it("keeps agent activity prompt previews visually constrained to one line", () => { const block = messageStreamBlocks({ activeThreadId: "thread", @@ -944,74 +773,6 @@ describe("work log renderer decisions", () => { expect(summary.textContent).not.toContain("donecompleted"); }); - it("renders the compact live agent summary as a Preact block", () => { - const parent = document.createElement("div"); - - renderMessageStreamBlocksInAct( - parent, - messageStreamBlocks({ - activeThreadId: "thread", - turnLifecycle: runningTurnLifecycle("turn"), - historyCursor: null, - loadingHistory: false, - displayItems: [ - { - id: "agent-1", - kind: "agent", - role: "tool", - text: "Wait for agent", - turnId: "turn", - tool: "wait", - status: "running", - senderThreadId: "parent", - receiverThreadIds: ["done", "running"], - prompt: null, - model: null, - reasoningEffort: null, - agents: [ - { threadId: "done", status: "completed", message: null }, - { threadId: "running", status: "running", message: "Inspecting renderer" }, - ], - }, - ], - openDetails: new Set(), - loadOlderTurns: vi.fn(), - renderMarkdown: (element, text) => element.createDiv({ text }), - }), - ); - - const summary = expectPresent(parent.querySelector('[data-codex-panel-block-key="live-agents:turn"]')); - expect(summary.querySelector(".codex-panel__agent-summary")?.textContent).toContain("Agents 1 running, 1 done"); - expect(summary.textContent).toContain("runningrunning: Inspecting renderer"); - unmountUiRootInAct(parent); - }); - - it("renders active reasoning as a Preact message stream block", () => { - const parent = document.createElement("div"); - - renderMessageStreamBlocksInAct( - parent, - messageStreamBlocks({ - activeThreadId: "thread", - turnLifecycle: runningTurnLifecycle("turn"), - historyCursor: null, - loadingHistory: false, - displayItems: [{ id: "reasoning-1", kind: "reasoning", role: "tool", text: "", turnId: "turn", status: "running" }], - openDetails: new Set(), - loadOlderTurns: vi.fn(), - renderMarkdown: (element, text) => element.createDiv({ text }), - }), - ); - - const reasoning = expectPresent( - parent.querySelector('[data-codex-panel-block-key="item:reasoning-1"] .codex-panel__reasoning'), - ); - expect(reasoning.classList.contains("is-active")).toBe(true); - expect(reasoning.querySelector(".codex-panel__reasoning-role")?.textContent).toBe("reasoning"); - expect(reasoning.querySelector(".codex-panel__reasoning-content")?.textContent).toBe("Reasoning..."); - unmountUiRootInAct(parent); - }); - it("renders context compaction as a one-line work item while running and after completion", () => { const runningParent = document.createElement("div"); const item: DisplayItem = { id: "compact-1", kind: "contextCompaction", role: "tool", text: "Context compaction", turnId: "turn" }; diff --git a/tests/features/chat/view-connection.test.ts b/tests/features/chat/view-connection.test.ts index 90ecc6d0..f35955ca 100644 --- a/tests/features/chat/view-connection.test.ts +++ b/tests/features/chat/view-connection.test.ts @@ -195,37 +195,6 @@ describe("CodexChatView connection lifecycle", () => { expect(siblingRoots.every((sibling) => !sibling.classList.contains("codex-panel") && sibling.childElementCount === 0)).toBe(true); }); - it("unmounts the chat shell from the view content root on close", async () => { - const view = await chatView(); - const siblingRoots = Array.from(view.containerEl.children).filter((child) => child !== view.contentEl); - - await view.onOpen(); - await view.onClose(); - - expect(view.contentEl.classList.contains("codex-panel")).toBe(true); - expect(view.contentEl.childElementCount).toBe(0); - expect(siblingRoots.every((sibling) => !sibling.classList.contains("codex-panel") && sibling.childElementCount === 0)).toBe(true); - }); - - it("wires open lifecycle registrations through the view controller host", async () => { - const addEventListener = vi.spyOn(document, "addEventListener"); - const cachedThreadList = vi.fn(() => null); - const cachedAppServerMetadata = vi.fn(() => null); - const host = chatHost({ - cachedThreadList, - cachedAppServerMetadata, - }); - const view = await chatView({ host }); - - await view.onOpen(); - - expect(addEventListener).toHaveBeenCalledWith("pointerdown", expect.any(Function)); - expect(cachedThreadList).toHaveBeenCalledOnce(); - expect(cachedAppServerMetadata).toHaveBeenCalledOnce(); - - addEventListener.mockRestore(); - }); - it("starts an empty thread when saving a toolbar goal from a blank panel", async () => { vi.useFakeTimers(); const client = connectedClient({