import { readFile } from 'fs/promises'; import path from 'path'; import React, { act } from 'react'; import { createRoot } from 'react-dom/client'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { OpenCodeTaskLogStreamSource } from '../../../../../src/main/services/team/taskLogs/stream/OpenCodeTaskLogStreamSource'; import { BoardTaskExactLogChunkBuilder } from '../../../../../src/main/services/team/taskLogs/exact/BoardTaskExactLogChunkBuilder'; import { TooltipProvider } from '../../../../../src/renderer/components/ui/tooltip'; import type { OpenCodeRuntimeTranscriptResponse } from '../../../../../src/main/services/runtime/ClaudeMultimodelBridgeService'; import type { BoardTaskLogStreamResponse, TeamTask } from '../../../../../src/shared/types'; const TEAM_NAME = 'relay-works-10'; const TASK_ID = '0b3a0624-5d66-4067-848e-5a74a1720c0d'; const FIXTURE_PATH = path.resolve( process.cwd(), 'test/fixtures/team/opencode/relay-works-10-jack-projection-transcript.json' ); const apiState = { getTaskLogStream: vi.fn< (teamName: string, taskId: string) => Promise >(), onTeamChange: vi.fn<(callback: (event: unknown, data: unknown) => void) => () => void>(), }; vi.mock('@renderer/api', () => ({ api: { teams: { getTaskLogStream: (...args: Parameters) => apiState.getTaskLogStream(...args), onTeamChange: (...args: Parameters) => apiState.onTeamChange(...args), }, }, })); import { TaskLogStreamSection } from '@renderer/components/team/taskLogs/TaskLogStreamSection'; const RELAY_WORKS_10_TASK: TeamTask = { id: TASK_ID, displayId: '0b3a0624', subject: 'Define calculator arithmetic behavior', owner: 'jack', status: 'completed', createdAt: '2026-04-24T20:29:03.133Z', updatedAt: '2026-04-24T20:29:34.157Z', workIntervals: [ { startedAt: '2026-04-24T20:29:03.133Z', completedAt: '2026-04-24T20:29:34.157Z', }, ], }; async function loadFixtureTranscript(): Promise< NonNullable > { const parsed = JSON.parse(await readFile(FIXTURE_PATH, 'utf8')) as OpenCodeRuntimeTranscriptResponse; if (parsed.providerId !== 'opencode' || !parsed.transcript) { throw new Error('Invalid OpenCode transcript fixture'); } return parsed.transcript; } async function buildFixtureStream(): Promise { const transcript = await loadFixtureTranscript(); const source = new OpenCodeTaskLogStreamSource( { getOpenCodeTranscript: vi.fn(async () => transcript), } as never, { resolve: async () => '/tmp/agent_teams_orchestrator' }, { getTasks: vi.fn(async () => [RELAY_WORKS_10_TASK]), getDeletedTasks: vi.fn(async () => []), } as never, new BoardTaskExactLogChunkBuilder(), { readTaskRecords: vi.fn(async () => []) } ); const stream = await source.getTaskLogStream(TEAM_NAME, TASK_ID); if (!stream) { throw new Error('Expected OpenCode fixture stream'); } return stream; } function flushMicrotasks(): Promise { return Promise.resolve(); } describe('TaskLogStreamSection OpenCode real fixture e2e', () => { afterEach(() => { document.body.innerHTML = ''; apiState.getTaskLogStream.mockReset(); apiState.onTeamChange.mockReset(); vi.unstubAllGlobals(); }); it('renders real OpenCode task activity through the UI log stream', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); apiState.onTeamChange.mockImplementation(() => () => undefined); apiState.getTaskLogStream.mockResolvedValueOnce(await buildFixtureStream()); const host = document.createElement('div'); document.body.appendChild(host); const root = createRoot(host); await act(async () => { root.render( React.createElement( TooltipProvider, null, React.createElement(TaskLogStreamSection, { teamName: TEAM_NAME, taskId: TASK_ID, liveEnabled: false, }) ) ); await flushMicrotasks(); await flushMicrotasks(); }); const text = host.textContent ?? ''; expect(text).toContain('Task Log Stream'); expect(text).toContain('matched task tool markers'); expect(text).toContain('Agent'); expect(text).toContain('Calculator behavior'); expect(text).toContain('Задача #0b3a0624 завершена'); expect(text).not.toContain('Keyboard handlers added'); expect(text).not.toContain('Logic smoke check'); expect(text).not.toContain('#00000000'); expect(text).not.toContain('SendMessage'); await act(async () => { root.unmount(); await flushMicrotasks(); }); }); });