// @vitest-environment jsdom import '../../test/dom-setup'; import '../../i18n'; import { describe, expect, it } from 'vitest'; import { screen } from '@testing-library/react'; import { renderWithProviders } from '../../test/render-helpers'; import { DelegateLiveConsole } from './DelegateLiveConsole'; import type { DelegateStreamEntry } from '../../hooks/useJobStream'; function entry(o: Partial = {}): DelegateStreamEntry { return { delegateRunId: 'r1', parentRunId: null, depth: 1, description: 'tweet 3 を深掘り', status: 'running', text: '論点を整理しています', currentTool: null, ...o, }; } describe('DelegateLiveConsole', () => { it('streams が空なら何も描画しない', () => { const { container } = renderWithProviders(); expect(container.firstChild).toBeNull(); }); it('実行中の delegate カードを description + ライブ文字つきで描画', () => { renderWithProviders(); expect(screen.getByText('tweet 3 を深掘り')).toBeTruthy(); expect(screen.getByText(/論点を整理しています/)).toBeTruthy(); }); it('currentTool があるとツール実行中を表示', () => { renderWithProviders(); expect(screen.getByText(/WebFetch/)).toBeTruthy(); }); it('完了した run は表示しない(履歴は概要に委ねる)— running だけ残す', () => { const streams = { done1: entry({ delegateRunId: 'done1', description: '完了したやつ', status: 'success' }), run1: entry({ delegateRunId: 'run1', description: '走ってるやつ', status: 'running' }), }; renderWithProviders(); expect(screen.queryByText('完了したやつ')).toBeNull(); expect(screen.getByText('走ってるやつ')).toBeTruthy(); }); it('全 run が完了済みなら何も描画しない', () => { const streams = { done1: entry({ delegateRunId: 'done1', status: 'success' }), done2: entry({ delegateRunId: 'done2', status: 'aborted' }), }; const { container } = renderWithProviders(); expect(container.firstChild).toBeNull(); }); it('入れ子(parentRunId あり)を子としてインデント描画', () => { const streams = { r1: entry({ delegateRunId: 'r1', description: '親', depth: 1 }), r2: entry({ delegateRunId: 'r2', parentRunId: 'r1', description: '子', depth: 2 }), }; renderWithProviders(); expect(screen.getByText('親')).toBeTruthy(); expect(screen.getByText('子')).toBeTruthy(); }); // --- Task 8: originJobId によるパーティション --- it('originJobId=null の親エントリと originJobId あり のサブタスクエントリを両方描画し、見出しを表示する', () => { const streams: Record = { 'run-parent': entry({ delegateRunId: 'run-parent', description: 'Parent delegate run', text: 'parent text output', originJobId: null, }), 'run-sub': entry({ delegateRunId: 'run-sub', description: 'Subtask delegate run', text: 'subtask text output', originJobId: 'sub1', }), }; renderWithProviders(); expect(screen.getByText('parent text output')).toBeTruthy(); expect(screen.getByText('subtask text output')).toBeTruthy(); // 'delegateRuns.subtaskSectionHeading' = 'Delegate runs in subtasks' expect(screen.getByText('Delegate runs in subtasks')).toBeTruthy(); }); it('サブタスクエントリが無ければ subtaskSectionHeading を描画しない', () => { const streams: Record = { 'run-parent': entry({ delegateRunId: 'run-parent', description: 'Only parent run', text: 'parent only', originJobId: null, }), }; renderWithProviders(); expect(screen.getByText('parent only')).toBeTruthy(); expect(screen.queryByText('Delegate runs in subtasks')).toBeNull(); }); it('サブタスクのみの場合も subtaskSectionHeading を表示する', () => { const streams: Record = { 'run-sub-only': entry({ delegateRunId: 'run-sub-only', description: 'Sub only run', text: 'sub only text', originJobId: 'job42', }), }; renderWithProviders(); expect(screen.getByText('sub only text')).toBeTruthy(); expect(screen.getByText('Delegate runs in subtasks')).toBeTruthy(); }); });