Files
automaker/apps/server/tests/unit/ui/summary-normalization.test.ts
gsxdsm 9747faf1b9 Fix agent output summary for pipeline steps (#812)
* Changes from fix/agent-output-summary-for-pipeline-steps

* feat: Optimize pipeline summary extraction and fix regex vulnerability

* fix: Use fallback summary for pipeline steps when extraction fails

* fix: Strip follow-up session scaffold from pipeline step fallback summaries
2026-02-25 22:13:38 -08:00

129 lines
5.2 KiB
TypeScript

/**
* Unit tests for summary normalization between UI components and parser functions.
*
* These tests verify that:
* - getFirstNonEmptySummary returns string | null
* - parseAllPhaseSummaries and isAccumulatedSummary expect string | undefined
* - The normalization (summary ?? undefined) correctly converts null to undefined
*
* This ensures the UI components properly bridge the type gap between:
* - getFirstNonEmptySummary (returns string | null)
* - parseAllPhaseSummaries (expects string | undefined)
* - isAccumulatedSummary (expects string | undefined)
*/
import { describe, it, expect } from 'vitest';
import { parseAllPhaseSummaries, isAccumulatedSummary } from '../../../../ui/src/lib/log-parser.ts';
import { getFirstNonEmptySummary } from '../../../../ui/src/lib/summary-selection.ts';
describe('Summary Normalization', () => {
describe('getFirstNonEmptySummary', () => {
it('should return the first non-empty string', () => {
const result = getFirstNonEmptySummary(null, undefined, 'valid summary', 'another');
expect(result).toBe('valid summary');
});
it('should return null when all candidates are empty', () => {
const result = getFirstNonEmptySummary(null, undefined, '', ' ');
expect(result).toBeNull();
});
it('should return null when no candidates provided', () => {
const result = getFirstNonEmptySummary();
expect(result).toBeNull();
});
it('should return null for all null/undefined candidates', () => {
const result = getFirstNonEmptySummary(null, undefined, null);
expect(result).toBeNull();
});
it('should preserve original string formatting (not trim)', () => {
const result = getFirstNonEmptySummary(' summary with spaces ');
expect(result).toBe(' summary with spaces ');
});
});
describe('parseAllPhaseSummaries with normalized input', () => {
it('should handle null converted to undefined via ?? operator', () => {
const summary = getFirstNonEmptySummary(null, undefined);
// This is the normalization: summary ?? undefined
const normalizedSummary = summary ?? undefined;
// TypeScript should accept this without error
const result = parseAllPhaseSummaries(normalizedSummary);
expect(result).toEqual([]);
});
it('should parse accumulated summary when non-null is normalized', () => {
const rawSummary =
'### Implementation\n\nDid some work\n\n---\n\n### Testing\n\nAll tests pass';
const summary = getFirstNonEmptySummary(null, rawSummary);
const normalizedSummary = summary ?? undefined;
const result = parseAllPhaseSummaries(normalizedSummary);
expect(result).toHaveLength(2);
expect(result[0].phaseName).toBe('Implementation');
expect(result[1].phaseName).toBe('Testing');
});
});
describe('isAccumulatedSummary with normalized input', () => {
it('should return false for null converted to undefined', () => {
const summary = getFirstNonEmptySummary(null, undefined);
const normalizedSummary = summary ?? undefined;
const result = isAccumulatedSummary(normalizedSummary);
expect(result).toBe(false);
});
it('should return true for valid accumulated summary after normalization', () => {
const rawSummary =
'### Implementation\n\nDid some work\n\n---\n\n### Testing\n\nAll tests pass';
const summary = getFirstNonEmptySummary(rawSummary);
const normalizedSummary = summary ?? undefined;
const result = isAccumulatedSummary(normalizedSummary);
expect(result).toBe(true);
});
it('should return false for single-phase summary after normalization', () => {
const rawSummary = '### Implementation\n\nDid some work';
const summary = getFirstNonEmptySummary(rawSummary);
const normalizedSummary = summary ?? undefined;
const result = isAccumulatedSummary(normalizedSummary);
expect(result).toBe(false);
});
});
describe('Type safety verification', () => {
it('should demonstrate that null must be normalized to undefined', () => {
// This test documents the type mismatch that requires normalization
const summary: string | null = getFirstNonEmptySummary(null);
const normalizedSummary: string | undefined = summary ?? undefined;
// parseAllPhaseSummaries expects string | undefined, not string | null
// The normalization converts null -> undefined, which is compatible
const result = parseAllPhaseSummaries(normalizedSummary);
expect(result).toEqual([]);
});
it('should work with the actual usage pattern from components', () => {
// Simulates the actual pattern used in summary-dialog.tsx and agent-output-modal.tsx
const featureSummary: string | null | undefined = null;
const extractedSummary: string | null | undefined = undefined;
const rawSummary = getFirstNonEmptySummary(featureSummary, extractedSummary);
const normalizedSummary = rawSummary ?? undefined;
// Both parser functions should work with the normalized value
const phases = parseAllPhaseSummaries(normalizedSummary);
const hasMultiple = isAccumulatedSummary(normalizedSummary);
expect(phases).toEqual([]);
expect(hasMultiple).toBe(false);
});
});
});