diff --git a/.github/workflows/pr-quality.yml b/.github/workflows/pr-quality.yml index 86ef6846..d6789e14 100644 --- a/.github/workflows/pr-quality.yml +++ b/.github/workflows/pr-quality.yml @@ -128,6 +128,9 @@ jobs: bun-version-file: package.json - name: Install root dependencies run: bun install --frozen-lockfile + - name: Install adapter dependencies + working-directory: adapters + run: bun install --frozen-lockfile - name: Run root runtime checks run: bun run check:server diff --git a/desktop/src/pages/TraceSession.test.tsx b/desktop/src/pages/TraceSession.test.tsx index 684f0078..22f03c33 100644 --- a/desktop/src/pages/TraceSession.test.tsx +++ b/desktop/src/pages/TraceSession.test.tsx @@ -1,4 +1,4 @@ -import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react' +import { act, cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react' import '@testing-library/jest-dom' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { TraceSession } from './TraceSession' @@ -503,16 +503,35 @@ describe('TraceSession', () => { }, calls: [baseTrace.calls[0]!, makeCall({ id: 'call-2', startedAt: '2026-06-09T10:00:08.000Z' })], }) - await renderReady(20) + // Advance polls only after each state is asserted. A real 20ms interval can + // invalidate the detail cache before the initial assertion under coverage. + vi.useFakeTimers({ toFake: ['setInterval', 'clearInterval'] }) + try { + await renderReady(20) - fireEvent.click(within(screen.getByTestId('trace-tree')).getByText('claude-sonnet-4-5')) - await waitFor(() => expect(sessionsApi.getTraceCall).toHaveBeenCalledTimes(1)) - await waitFor(() => expect(vi.mocked(sessionsApi.getTrace).mock.calls.length).toBeGreaterThanOrEqual(3)) + fireEvent.click(within(screen.getByTestId('trace-tree')).getByText('claude-sonnet-4-5')) + await waitFor(() => expect(sessionsApi.getTraceCall).toHaveBeenCalledTimes(1)) + expect(sessionsApi.getTrace).toHaveBeenCalledTimes(1) + expect(sessionsApi.getMessages).toHaveBeenCalledTimes(1) - await screen.findByText('claude-sonnet-4-5 x2') - expect(vi.mocked(sessionsApi.getTraceCall).mock.calls.length).toBeGreaterThan(1) - const detail = within(screen.getByTestId('trace-detail')) - expect(detail.getByRole('heading', { level: 2, name: 'claude-sonnet-4-5' })).toBeInTheDocument() + for (const expectedTraceCalls of [2, 3]) { + await act(async () => { await vi.advanceTimersByTimeAsync(20) }) + expect(sessionsApi.getTrace).toHaveBeenCalledTimes(expectedTraceCalls) + expect(sessionsApi.getMessages).toHaveBeenCalledTimes(1) + expect(screen.queryByText('claude-sonnet-4-5 x2')).not.toBeInTheDocument() + } + + await act(async () => { await vi.advanceTimersByTimeAsync(20) }) + expect(sessionsApi.getTrace).toHaveBeenCalledTimes(4) + expect(sessionsApi.getMessages).toHaveBeenCalledTimes(2) + expect(screen.getByText('claude-sonnet-4-5 x2')).toBeInTheDocument() + expect(vi.mocked(sessionsApi.getTraceCall).mock.calls.length).toBeGreaterThan(1) + const detail = within(screen.getByTestId('trace-detail')) + expect(detail.getByRole('heading', { level: 2, name: 'claude-sonnet-4-5' })).toBeInTheDocument() + } finally { + cleanup() + vi.useRealTimers() + } }) it('does not refetch full messages for identical trace polls', async () => { diff --git a/scripts/pr/pr-quality-workflow.test.ts b/scripts/pr/pr-quality-workflow.test.ts index 9acff32c..95ef9eb5 100644 --- a/scripts/pr/pr-quality-workflow.test.ts +++ b/scripts/pr/pr-quality-workflow.test.ts @@ -4,7 +4,7 @@ import { parse } from 'yaml' type WorkflowJob = { needs?: string | string[] - steps?: Array<{ name?: string; run?: string }> + steps?: Array<{ name?: string; run?: string; 'working-directory'?: string }> } function workflowJobs(workflow: string) { @@ -70,6 +70,17 @@ describe('PR quality workflow', () => { } }) + test('installs adapter dependencies before root server tests that import adapters', () => { + const jobs = workflowJobs(readFileSync('.github/workflows/pr-quality.yml', 'utf8')) + const steps = jobs['server-checks'].steps ?? [] + const adapterInstall = steps.findIndex(step => + step['working-directory'] === 'adapters' && step.run === 'bun install --frozen-lockfile', + ) + const serverCheck = steps.findIndex(step => step.run === 'bun run check:server') + expect(adapterInstall).toBeGreaterThanOrEqual(0) + expect(adapterInstall).toBeLessThan(serverCheck) + }) + test('keeps coverage artifacts observable in CI', () => { const workflow = readFileSync('.github/workflows/pr-quality.yml', 'utf8')