Skip to content

Commit 3280330

Browse files
committed
test: add tests for per-test timing events and showTestResults flag
1 parent 8c180f8 commit 3280330

4 files changed

Lines changed: 123 additions & 0 deletions

File tree

src/rendering/__tests__/text-render-parity.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
22
import type { PipelineEvent } from '../../types/pipeline-events.ts';
33
import { renderEvents } from '../render.ts';
44
import { createCliTextRenderer } from '../../utils/renderers/cli-text-renderer.ts';
5+
import { renderCliTextTranscript } from '../../utils/renderers/cli-text-renderer.ts';
56

67
function captureCliText(events: readonly PipelineEvent[]): string {
78
const stdoutWrite = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
@@ -168,4 +169,37 @@ describe('text render parity', () => {
168169
expect(output).toContain('xcodebuildmcp macos get-app-path --scheme "MCPTest"');
169170
expect(output).not.toContain('get_mac_app_path({');
170171
});
172+
173+
it('omits per-test results by default and includes them when showTestResults is true', () => {
174+
const events: PipelineEvent[] = [
175+
{
176+
type: 'test-case-result',
177+
timestamp: '2026-04-14T00:00:00.000Z',
178+
operation: 'TEST',
179+
suite: 'Suite',
180+
test: 'testA',
181+
status: 'passed',
182+
durationMs: 100,
183+
},
184+
{
185+
type: 'summary',
186+
timestamp: '2026-04-14T00:00:01.000Z',
187+
operation: 'TEST',
188+
status: 'SUCCEEDED',
189+
totalTests: 1,
190+
passedTests: 1,
191+
skippedTests: 0,
192+
durationMs: 100,
193+
},
194+
];
195+
196+
const withoutFlag = renderCliTextTranscript(events);
197+
expect(withoutFlag).not.toContain('Test Results:');
198+
expect(withoutFlag).toContain('1 test passed');
199+
200+
const withFlag = renderCliTextTranscript(events, { showTestResults: true });
201+
expect(withFlag).toContain('Test Results:');
202+
expect(withFlag).toContain('Suite/testA (0.100s)');
203+
expect(withFlag).toContain('1 test passed');
204+
});
171205
});

src/utils/__tests__/xcodebuild-event-parser.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,4 +304,34 @@ describe('xcodebuild-event-parser', () => {
304304
const statusEvents = events.filter((e) => e.type === 'build-stage');
305305
expect(statusEvents.length).toBeLessThanOrEqual(1);
306306
});
307+
308+
it('emits test-case-result events with per-test timing for all statuses', () => {
309+
const events = collectEvents('TEST', [
310+
{ source: 'stdout', text: "Test Case '-[Suite testA]' passed (0.001 seconds)\n" },
311+
{ source: 'stdout', text: "Test Case '-[Suite testB]' failed (0.002 seconds)\n" },
312+
{ source: 'stdout', text: "Test Case '-[Suite testC]' skipped (0.000 seconds)\n" },
313+
]);
314+
315+
const results = events.filter((e) => e.type === 'test-case-result');
316+
expect(results).toHaveLength(3);
317+
expect(results[0]).toMatchObject({
318+
type: 'test-case-result',
319+
operation: 'TEST',
320+
suite: 'Suite',
321+
test: 'testA',
322+
status: 'passed',
323+
durationMs: 1,
324+
});
325+
expect(results[1]).toMatchObject({ test: 'testB', status: 'failed', durationMs: 2 });
326+
expect(results[2]).toMatchObject({ test: 'testC', status: 'skipped', durationMs: 0 });
327+
});
328+
329+
it('does not emit test-case-result for BUILD operations', () => {
330+
const events = collectEvents('BUILD', [
331+
{ source: 'stdout', text: "Test Case '-[Suite testA]' passed (0.001 seconds)\n" },
332+
]);
333+
334+
const results = events.filter((e) => e.type === 'test-case-result');
335+
expect(results).toHaveLength(0);
336+
});
307337
});

src/utils/__tests__/xcodebuild-run-state.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,4 +404,32 @@ describe('xcodebuild-run-state', () => {
404404
expect(forwarded[0].type).toBe('header');
405405
expect(forwarded[1].type).toBe('next-steps');
406406
});
407+
408+
it('collects test-case-result events', () => {
409+
const state = createXcodebuildRunState({ operation: 'TEST' });
410+
411+
state.push({
412+
type: 'test-case-result',
413+
timestamp: ts(),
414+
operation: 'TEST',
415+
suite: 'Suite',
416+
test: 'testA',
417+
status: 'passed',
418+
durationMs: 100,
419+
});
420+
state.push({
421+
type: 'test-case-result',
422+
timestamp: ts(),
423+
operation: 'TEST',
424+
suite: 'Suite',
425+
test: 'testB',
426+
status: 'failed',
427+
durationMs: 200,
428+
});
429+
430+
const snap = state.snapshot();
431+
expect(snap.testCaseResults).toHaveLength(2);
432+
expect(snap.testCaseResults[0]).toMatchObject({ test: 'testA', status: 'passed' });
433+
expect(snap.testCaseResults[1]).toMatchObject({ test: 'testB', status: 'failed' });
434+
});
407435
});

src/utils/renderers/__tests__/event-formatting.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
formatStatusLineEvent,
1313
formatDetailTreeEvent,
1414
formatTransientStatusLineEvent,
15+
formatTestCaseResults,
1516
} from '../event-formatting.ts';
1617

1718
describe('event formatting', () => {
@@ -277,4 +278,34 @@ describe('event formatting', () => {
277278
expect(rendered).toContain(' - XCTAssertEqual failed');
278279
expect(rendered).toContain(' - Expected 4, got 5');
279280
});
281+
282+
it('formats per-test case results with status icons and durations', () => {
283+
const rendered = formatTestCaseResults([
284+
{
285+
type: 'test-case-result',
286+
timestamp: '',
287+
operation: 'TEST',
288+
suite: 'MathTests',
289+
test: 'testAdd',
290+
status: 'passed',
291+
durationMs: 1234,
292+
},
293+
{
294+
type: 'test-case-result',
295+
timestamp: '',
296+
operation: 'TEST',
297+
test: 'testOrphan',
298+
status: 'skipped',
299+
},
300+
]);
301+
302+
expect(rendered).toContain('Test Results:');
303+
expect(rendered).toContain('\u{2705} MathTests/testAdd (1.234s)');
304+
expect(rendered).toContain('\u{23ED}\u{FE0F} testOrphan');
305+
expect(rendered).not.toContain('testOrphan (');
306+
});
307+
308+
it('returns empty string for no test case results', () => {
309+
expect(formatTestCaseResults([])).toBe('');
310+
});
280311
});

0 commit comments

Comments
 (0)