File size: 5,734 Bytes
52efc7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect } from 'vitest';
import { renderWithProviders } from '../../../test-utils/render.js';
import { ThinkingMessage } from './ThinkingMessage.js';
import React from 'react';

describe('ThinkingMessage', () => {
  it('renders subject line with vertical rule and "Thinking..." header', async () => {
    const renderResult = await renderWithProviders(
      <ThinkingMessage
        thought={{ subject: 'Planning', description: 'test' }}
        terminalWidth={80}
        isFirstThinking={true}
      />,
    );
    await renderResult.waitUntilReady();

    const output = renderResult.lastFrame();
    expect(output).toContain(' Thinking...');
    expect(output).toContain('│');
    expect(output).toContain('Planning');
    expect(output).toMatchSnapshot();
    await expect(renderResult).toMatchSvgSnapshot();
    renderResult.unmount();
  });

  it('uses description when subject is empty', async () => {
    const renderResult = await renderWithProviders(
      <ThinkingMessage
        thought={{ subject: '', description: 'Processing details' }}
        terminalWidth={80}
        isFirstThinking={true}
      />,
    );
    await renderResult.waitUntilReady();

    const output = renderResult.lastFrame();
    expect(output).toContain('Processing details');
    expect(output).toContain('│');
    expect(output).toMatchSnapshot();
    await expect(renderResult).toMatchSvgSnapshot();
    renderResult.unmount();
  });

  it('renders full mode with left border and full text', async () => {
    const renderResult = await renderWithProviders(
      <ThinkingMessage
        thought={{
          subject: 'Planning',
          description: 'I am planning the solution.',
        }}
        terminalWidth={80}
        isFirstThinking={true}
      />,
    );
    await renderResult.waitUntilReady();

    const output = renderResult.lastFrame();
    expect(output).toContain('│');
    expect(output).toContain('Planning');
    expect(output).toContain('I am planning the solution.');
    expect(output).toMatchSnapshot();
    await expect(renderResult).toMatchSvgSnapshot();
    renderResult.unmount();
  });

  it('renders "Thinking..." header when isFirstThinking is true', async () => {
    const renderResult = await renderWithProviders(
      <ThinkingMessage
        thought={{
          subject: 'Summary line',
          description: 'First body line',
        }}
        terminalWidth={80}
        isFirstThinking={true}
      />,
    );
    await renderResult.waitUntilReady();

    const output = renderResult.lastFrame();
    expect(output).toContain(' Thinking...');
    expect(output).toContain('Summary line');
    expect(output).toContain('│');
    expect(output).toMatchSnapshot();
    await expect(renderResult).toMatchSvgSnapshot();
    renderResult.unmount();
  });

  it('normalizes escaped newline tokens', async () => {
    const renderResult = await renderWithProviders(
      <ThinkingMessage
        thought={{
          subject: 'Matching the Blocks',
          description: '\\n\\nSome more text',
        }}
        terminalWidth={80}
        isFirstThinking={true}
      />,
    );
    await renderResult.waitUntilReady();

    expect(renderResult.lastFrame()).toMatchSnapshot();
    await expect(renderResult).toMatchSvgSnapshot();
    renderResult.unmount();
  });

  it('renders empty state gracefully', async () => {
    const renderResult = await renderWithProviders(
      <ThinkingMessage
        thought={{ subject: '', description: '' }}
        terminalWidth={80}
        isFirstThinking={true}
      />,
    );
    await renderResult.waitUntilReady();

    expect(renderResult.lastFrame({ allowEmpty: true })).toBe('');
    renderResult.unmount();
  });

  it('renders multiple thinking messages sequentially correctly', async () => {
    const renderResult = await renderWithProviders(
      <React.Fragment>
        <ThinkingMessage
          thought={{
            subject: 'Initial analysis',
            description:
              'This is a multiple line paragraph for the first thinking message of how the model analyzes the problem.',
          }}
          terminalWidth={80}
          isFirstThinking={true}
        />
        <ThinkingMessage
          thought={{
            subject: 'Planning execution',
            description:
              'This a second multiple line paragraph for the second thinking message explaining the plan in detail so that it wraps around the terminal display.',
          }}
          terminalWidth={80}
        />
        <ThinkingMessage
          thought={{
            subject: 'Refining approach',
            description:
              'And finally a third multiple line paragraph for the third thinking message to refine the solution.',
          }}
          terminalWidth={80}
        />
      </React.Fragment>,
    );
    await renderResult.waitUntilReady();

    expect(renderResult.lastFrame()).toMatchSnapshot();
    await expect(renderResult).toMatchSvgSnapshot();
    renderResult.unmount();
  });

  it('filters out progress dots and empty lines', async () => {
    const renderResult = await renderWithProviders(
      <ThinkingMessage
        thought={{ subject: '...', description: 'Thinking\n.\n..\n...\nDone' }}
        terminalWidth={80}
        isFirstThinking={true}
      />,
    );
    await renderResult.waitUntilReady();

    const output = renderResult.lastFrame();
    expect(output).toContain('Thinking');
    expect(output).toContain('Done');
    expect(renderResult.lastFrame()).toMatchSnapshot();
    await expect(renderResult).toMatchSvgSnapshot();
    renderResult.unmount();
  });
});