File size: 5,340 Bytes
1dbc34b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
 * 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);
    });
  });
});