Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@ import {
useBackgroundTaskViewState,
} from '../../contexts/BackgroundTaskViewContext.js';
import { ConfigContext } from '../../contexts/ConfigContext.js';
import { useBackgroundTaskView } from '../../hooks/useBackgroundTaskView.js';
import {
useBackgroundTaskView,
type DialogEntry,
} from '../../hooks/useBackgroundTaskView.js';
import { useKeypress } from '../../hooks/useKeypress.js';

vi.mock('../../hooks/useBackgroundTaskView.js', () => ({
useBackgroundTaskView: vi.fn(),
// Re-export the helper so Dialog renderers can still resolve it under the
// mocked module. Inline impl keeps the test independent of the hook
// module while preserving the discriminator-based id contract.
entryId: (entry: DialogEntry): string =>
entry.kind === 'agent' ? entry.agentId : entry.shellId,
}));

vi.mock('../../hooks/useKeypress.js', () => ({
Expand All @@ -30,44 +38,55 @@ vi.mock('../../hooks/useKeypress.js', () => ({
const mockedUseBackgroundTaskView = vi.mocked(useBackgroundTaskView);
const mockedUseKeypress = vi.mocked(useKeypress);

function entry(overrides: Partial<BackgroundTaskEntry>): BackgroundTaskEntry {
function entry(overrides: Partial<BackgroundTaskEntry> = {}): DialogEntry {
return {
kind: 'agent',
agentId: 'a',
description: 'desc',
status: 'running',
startTime: 0,
abortController: new AbortController(),
...overrides,
};
} as DialogEntry;
}

interface ProbeHandle {
actions: ReturnType<typeof useBackgroundTaskViewActions>;
state: ReturnType<typeof useBackgroundTaskViewState>;
setEntries: (next: readonly BackgroundTaskEntry[]) => void;
setEntries: (next: readonly DialogEntry[]) => void;
}

interface Harness {
cancel: ReturnType<typeof vi.fn>;
setEntries: (next: readonly BackgroundTaskEntry[]) => void;
setEntries: (next: readonly DialogEntry[]) => void;
pressKey: (key: { name?: string; sequence?: string }) => void;
call: (fn: () => void) => void;
lastFrame: () => string | undefined;
probe: { current: ProbeHandle | null };
}

function setup(initial: readonly BackgroundTaskEntry[]): Harness {
function setup(initial: readonly DialogEntry[]): Harness {
const handlers: Array<(key: { name?: string; sequence?: string }) => void> =
[];
mockedUseKeypress.mockImplementation((cb, opts) => {
if (opts?.isActive !== false) handlers.push(cb as never);
});

const cancel = vi.fn();
// Stub registry that resolves `.get(agentId)` against the current entries
// snapshot — the dialog now re-reads agent entries via `.get()` to pick up
// live activity/stats mutations the snapshot misses.
let currentEntries: readonly DialogEntry[] = initial;
const config = {
getBackgroundTaskRegistry: () => ({
cancel,
setActivityChangeCallback: vi.fn(),
get: (id: string) => {
const match = currentEntries.find(
(e) => e.kind === 'agent' && e.agentId === id,
);
return match;
},
}),
} as unknown as Config;

Expand All @@ -94,7 +113,7 @@ function setup(initial: readonly BackgroundTaskEntry[]): Harness {
function Probe({
entriesSetter,
}: {
entriesSetter: (e: readonly BackgroundTaskEntry[]) => void;
entriesSetter: (e: readonly DialogEntry[]) => void;
}) {
handle.current = {
actions: useBackgroundTaskViewActions(),
Expand All @@ -110,6 +129,7 @@ function setup(initial: readonly BackgroundTaskEntry[]): Harness {
cancel,
setEntries(next) {
handlers.length = 0;
currentEntries = next;
act(() => handle.current!.setEntries(next));
},
pressKey(key) {
Expand Down
Loading
Loading