Migrate core render util to use xterm.js as part of the rendering loop. (#19044)

This commit is contained in:
Jacob Richman
2026-02-18 16:46:50 -08:00
committed by GitHub
parent 577ee98593
commit 85a48203db
213 changed files with 7065 additions and 3852 deletions
@@ -25,11 +25,12 @@ describe('<CompressionMessage />', () => {
});
describe('pending state', () => {
it('renders pending message when compression is in progress', () => {
it('renders pending message when compression is in progress', async () => {
const props = createCompressionProps({ isPending: true });
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<CompressionMessage {...props} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Compressing chat history');
@@ -38,16 +39,17 @@ describe('<CompressionMessage />', () => {
});
describe('normal compression (successful token reduction)', () => {
it('renders success message when tokens are reduced', () => {
it('renders success message when tokens are reduced', async () => {
const props = createCompressionProps({
isPending: false,
originalTokenCount: 100,
newTokenCount: 50,
compressionStatus: CompressionStatus.COMPRESSED,
});
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<CompressionMessage {...props} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('✦');
@@ -57,22 +59,22 @@ describe('<CompressionMessage />', () => {
unmount();
});
it('renders success message for large successful compressions', () => {
const testCases = [
{ original: 50000, new: 25000 }, // Large compression
{ original: 700000, new: 350000 }, // Very large compression
];
for (const { original, new: newTokens } of testCases) {
it.each([
{ original: 50000, newTokens: 25000 }, // Large compression
{ original: 700000, newTokens: 350000 }, // Very large compression
])(
'renders success message for large successful compression (from $original to $newTokens)',
async ({ original, newTokens }) => {
const props = createCompressionProps({
isPending: false,
originalTokenCount: original,
newTokenCount: newTokens,
compressionStatus: CompressionStatus.COMPRESSED,
});
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<CompressionMessage {...props} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('✦');
@@ -82,12 +84,12 @@ describe('<CompressionMessage />', () => {
expect(output).not.toContain('Skipping compression');
expect(output).not.toContain('did not reduce size');
unmount();
}
});
},
);
});
describe('skipped compression (tokens increased or same)', () => {
it('renders skip message when compression would increase token count', () => {
it('renders skip message when compression would increase token count', async () => {
const props = createCompressionProps({
isPending: false,
originalTokenCount: 50,
@@ -95,9 +97,10 @@ describe('<CompressionMessage />', () => {
compressionStatus:
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
});
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<CompressionMessage {...props} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('✦');
@@ -107,7 +110,7 @@ describe('<CompressionMessage />', () => {
unmount();
});
it('renders skip message when token counts are equal', () => {
it('renders skip message when token counts are equal', async () => {
const props = createCompressionProps({
isPending: false,
originalTokenCount: 50,
@@ -115,9 +118,10 @@ describe('<CompressionMessage />', () => {
compressionStatus:
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
});
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<CompressionMessage {...props} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain(
@@ -128,50 +132,49 @@ describe('<CompressionMessage />', () => {
});
describe('message content validation', () => {
it('displays correct compression statistics', () => {
const testCases = [
{
original: 200,
new: 80,
expected: 'compressed from 200 to 80 tokens',
},
{
original: 500,
new: 150,
expected: 'compressed from 500 to 150 tokens',
},
{
original: 1500,
new: 400,
expected: 'compressed from 1500 to 400 tokens',
},
];
for (const { original, new: newTokens, expected } of testCases) {
it.each([
{
original: 200,
newTokens: 80,
expected: 'compressed from 200 to 80 tokens',
},
{
original: 500,
newTokens: 150,
expected: 'compressed from 500 to 150 tokens',
},
{
original: 1500,
newTokens: 400,
expected: 'compressed from 1500 to 400 tokens',
},
])(
'displays correct compression statistics (from $original to $newTokens)',
async ({ original, newTokens, expected }) => {
const props = createCompressionProps({
isPending: false,
originalTokenCount: original,
newTokenCount: newTokens,
compressionStatus: CompressionStatus.COMPRESSED,
});
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<CompressionMessage {...props} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain(expected);
unmount();
}
});
},
);
it('shows skip message for small histories when new tokens >= original tokens', () => {
const testCases = [
{ original: 50, new: 60 }, // Increased
{ original: 100, new: 100 }, // Same
{ original: 49999, new: 50000 }, // Just under 50k threshold
];
for (const { original, new: newTokens } of testCases) {
it.each([
{ original: 50, newTokens: 60 }, // Increased
{ original: 100, newTokens: 100 }, // Same
{ original: 49999, newTokens: 50000 }, // Just under 50k threshold
])(
'shows skip message for small histories when new tokens >= original tokens ($original -> $newTokens)',
async ({ original, newTokens }) => {
const props = createCompressionProps({
isPending: false,
originalTokenCount: original,
@@ -179,9 +182,10 @@ describe('<CompressionMessage />', () => {
compressionStatus:
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
});
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<CompressionMessage {...props} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain(
@@ -189,17 +193,16 @@ describe('<CompressionMessage />', () => {
);
expect(output).not.toContain('compressed from');
unmount();
}
});
},
);
it('shows compression failure message for large histories when new tokens >= original tokens', () => {
const testCases = [
{ original: 50000, new: 50100 }, // At 50k threshold
{ original: 700000, new: 710000 }, // Large history case
{ original: 100000, new: 100000 }, // Large history, same count
];
for (const { original, new: newTokens } of testCases) {
it.each([
{ original: 50000, newTokens: 50100 }, // At 50k threshold
{ original: 700000, newTokens: 710000 }, // Large history case
{ original: 100000, newTokens: 100000 }, // Large history, same count
])(
'shows compression failure message for large histories when new tokens >= original tokens ($original -> $newTokens)',
async ({ original, newTokens }) => {
const props = createCompressionProps({
isPending: false,
originalTokenCount: original,
@@ -207,28 +210,30 @@ describe('<CompressionMessage />', () => {
compressionStatus:
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
});
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<CompressionMessage {...props} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('compression did not reduce size');
expect(output).not.toContain('compressed from');
expect(output).not.toContain('Compression was not beneficial');
unmount();
}
});
},
);
});
describe('failure states', () => {
it('renders failure message when model returns an empty summary', () => {
it('renders failure message when model returns an empty summary', async () => {
const props = createCompressionProps({
isPending: false,
compressionStatus: CompressionStatus.COMPRESSION_FAILED_EMPTY_SUMMARY,
});
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<CompressionMessage {...props} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('✦');
@@ -238,15 +243,16 @@ describe('<CompressionMessage />', () => {
unmount();
});
it('renders failure message for token count errors', () => {
it('renders failure message for token count errors', async () => {
const props = createCompressionProps({
isPending: false,
compressionStatus:
CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR,
});
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<CompressionMessage {...props} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain(
@@ -9,18 +9,26 @@ import { ErrorMessage } from './ErrorMessage.js';
import { describe, it, expect } from 'vitest';
describe('ErrorMessage', () => {
it('renders with the correct prefix and text', () => {
const { lastFrame } = render(<ErrorMessage text="Something went wrong" />);
it('renders with the correct prefix and text', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
<ErrorMessage text="Something went wrong" />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('renders multiline error messages', () => {
it('renders multiline error messages', async () => {
const message = 'Error line 1\nError line 2';
const { lastFrame } = render(<ErrorMessage text={message} />);
const { lastFrame, waitUntilReady, unmount } = render(
<ErrorMessage text={message} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
});
@@ -23,35 +23,39 @@ describe('<GeminiMessage /> - Raw Markdown Display Snapshots', () => {
},
])(
'renders with renderMarkdown=$renderMarkdown $description',
({ renderMarkdown }) => {
const { lastFrame } = renderWithProviders(
async ({ renderMarkdown }) => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<GeminiMessage {...baseProps} />,
{
uiState: { renderMarkdown, streamingState: StreamingState.Idle },
},
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
},
);
it.each([{ renderMarkdown: true }, { renderMarkdown: false }])(
'renders pending state with renderMarkdown=$renderMarkdown',
({ renderMarkdown }) => {
const { lastFrame } = renderWithProviders(
async ({ renderMarkdown }) => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<GeminiMessage {...baseProps} isPending={true} />,
{
uiState: { renderMarkdown, streamingState: StreamingState.Idle },
},
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
},
);
it('wraps long lines correctly in raw markdown mode', () => {
it('wraps long lines correctly in raw markdown mode', async () => {
const terminalWidth = 20;
const text =
'This is a long line that should wrap correctly without truncation';
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<GeminiMessage
text={text}
isPending={false}
@@ -61,6 +65,8 @@ describe('<GeminiMessage /> - Raw Markdown Display Snapshots', () => {
uiState: { renderMarkdown: false, streamingState: StreamingState.Idle },
},
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
});
@@ -9,27 +9,37 @@ import { InfoMessage } from './InfoMessage.js';
import { describe, it, expect } from 'vitest';
describe('InfoMessage', () => {
it('renders with the correct default prefix and text', () => {
const { lastFrame } = render(<InfoMessage text="Just so you know" />);
it('renders with the correct default prefix and text', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
<InfoMessage text="Just so you know" />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('renders with a custom icon', () => {
const { lastFrame } = render(
it('renders with a custom icon', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
<InfoMessage text="Custom icon test" icon="★" />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('renders multiline info messages', () => {
it('renders multiline info messages', async () => {
const message = 'Info line 1\nInfo line 2';
const { lastFrame } = render(<InfoMessage text={message} />);
const { lastFrame, waitUntilReady, unmount } = render(
<InfoMessage text={message} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
});
@@ -23,7 +23,7 @@ describe('ToolConfirmationMessage Redirection', () => {
getIdeMode: () => false,
} as unknown as Config;
it('should display redirection warning and tip for redirected commands', () => {
it('should display redirection warning and tip for redirected commands', async () => {
const confirmationDetails: SerializableConfirmationDetails = {
type: 'exec',
title: 'Confirm Shell Command',
@@ -32,7 +32,7 @@ describe('ToolConfirmationMessage Redirection', () => {
rootCommands: ['echo'],
};
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={confirmationDetails}
@@ -41,8 +41,10 @@ describe('ToolConfirmationMessage Redirection', () => {
terminalWidth={100}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
});
@@ -65,7 +65,7 @@ describe('<ShellToolMessage />', () => {
['SHELL_COMMAND_NAME', SHELL_COMMAND_NAME],
['SHELL_TOOL_NAME', SHELL_TOOL_NAME],
])('clicks inside the shell area sets focus for %s', async (_, name) => {
const { stdin, lastFrame, simulateClick } = renderShell(
const { lastFrame, simulateClick } = renderShell(
{ name },
{ mouseEventsEnabled: true },
);
@@ -74,7 +74,7 @@ describe('<ShellToolMessage />', () => {
expect(lastFrame()).toContain('A shell command');
});
await simulateClick(stdin, 2, 2);
await simulateClick(2, 2);
await waitFor(() => {
expect(mockSetEmbeddedShellFocused).toHaveBeenCalledWith(true);
@@ -164,10 +164,9 @@ describe('<ShellToolMessage />', () => {
},
],
])('%s', async (_, props, options) => {
const { lastFrame } = renderShell(props, options);
await waitFor(() => {
expect(lastFrame()).toMatchSnapshot();
});
const { lastFrame, waitUntilReady } = renderShell(props, options);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
});
@@ -198,7 +197,7 @@ describe('<ShellToolMessage />', () => {
false,
],
])('%s', async (_, availableTerminalHeight, expectedMaxLines, focused) => {
const { lastFrame } = renderShell(
const { lastFrame, waitUntilReady } = renderShell(
{
resultDisplay: LONG_OUTPUT,
renderOutputAsMarkdown: false,
@@ -215,11 +214,10 @@ describe('<ShellToolMessage />', () => {
},
);
await waitFor(() => {
const frame = lastFrame();
expect(frame!.match(/Line \d+/g)?.length).toBe(expectedMaxLines);
expect(frame).toMatchSnapshot();
});
await waitUntilReady();
const frame = lastFrame();
expect(frame.match(/Line \d+/g)?.length).toBe(expectedMaxLines);
expect(frame).toMatchSnapshot();
});
});
});
@@ -9,28 +9,32 @@ import { renderWithProviders } from '../../../test-utils/render.js';
import { ThinkingMessage } from './ThinkingMessage.js';
describe('ThinkingMessage', () => {
it('renders subject line', () => {
const { lastFrame } = renderWithProviders(
it('renders subject line', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ThinkingMessage
thought={{ subject: 'Planning', description: 'test' }}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('uses description when subject is empty', () => {
const { lastFrame } = renderWithProviders(
it('uses description when subject is empty', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ThinkingMessage
thought={{ subject: '', description: 'Processing details' }}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders full mode with left border and full text', () => {
const { lastFrame } = renderWithProviders(
it('renders full mode with left border and full text', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ThinkingMessage
thought={{
subject: 'Planning',
@@ -38,12 +42,14 @@ describe('ThinkingMessage', () => {
}}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('indents summary line correctly', () => {
const { lastFrame } = renderWithProviders(
it('indents summary line correctly', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ThinkingMessage
thought={{
subject: 'Summary line',
@@ -51,12 +57,14 @@ describe('ThinkingMessage', () => {
}}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('normalizes escaped newline tokens', () => {
const { lastFrame } = renderWithProviders(
it('normalizes escaped newline tokens', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ThinkingMessage
thought={{
subject: 'Matching the Blocks',
@@ -64,15 +72,19 @@ describe('ThinkingMessage', () => {
}}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders empty state gracefully', () => {
const { lastFrame } = renderWithProviders(
it('renders empty state gracefully', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ThinkingMessage thought={{ subject: '', description: '' }} />,
);
await waitUntilReady();
expect(lastFrame()).toBe('');
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
});
@@ -32,29 +32,37 @@ const createTodoHistoryItem = (todos: Todo[]): HistoryItem =>
describe.each([true, false])(
'<TodoTray /> (showFullTodos: %s)',
(showFullTodos: boolean) => {
const renderWithUiState = (uiState: Partial<UIState>) =>
render(
async (showFullTodos: boolean) => {
const renderWithUiState = async (uiState: Partial<UIState>) => {
const result = render(
<UIStateContext.Provider value={uiState as UIState}>
<TodoTray />
</UIStateContext.Provider>,
);
await result.waitUntilReady();
return result;
};
it('renders null when no todos are in the history', () => {
const { lastFrame } = renderWithUiState({ history: [], showFullTodos });
expect(lastFrame()).toMatchSnapshot();
it('renders null when no todos are in the history', async () => {
const { lastFrame, unmount } = await renderWithUiState({
history: [],
showFullTodos,
});
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
it('renders null when todo list is empty', () => {
const { lastFrame } = renderWithUiState({
it('renders null when todo list is empty', async () => {
const { lastFrame, unmount } = await renderWithUiState({
history: [createTodoHistoryItem([])],
showFullTodos,
});
expect(lastFrame()).toMatchSnapshot();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
it('renders when todos exist but none are in progress', () => {
const { lastFrame } = renderWithUiState({
it('renders when todos exist but none are in progress', async () => {
const { lastFrame, unmount } = await renderWithUiState({
history: [
createTodoHistoryItem([
{ description: 'Pending Task', status: 'pending' },
@@ -65,10 +73,11 @@ describe.each([true, false])(
showFullTodos,
});
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders when todos exist and one is in progress', () => {
const { lastFrame } = renderWithUiState({
it('renders when todos exist and one is in progress', async () => {
const { lastFrame, unmount } = await renderWithUiState({
history: [
createTodoHistoryItem([
{ description: 'Pending Task', status: 'pending' },
@@ -80,10 +89,11 @@ describe.each([true, false])(
showFullTodos,
});
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders a todo list with long descriptions that wrap when full view is on', () => {
const { lastFrame } = render(
it('renders a todo list with long descriptions that wrap when full view is on', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
<Box width="50">
<UIStateContext.Provider
value={
@@ -110,11 +120,13 @@ describe.each([true, false])(
</UIStateContext.Provider>
</Box>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders the most recent todo list when multiple write_todos calls are in history', () => {
const { lastFrame } = renderWithUiState({
it('renders the most recent todo list when multiple write_todos calls are in history', async () => {
const { lastFrame, unmount } = await renderWithUiState({
history: [
createTodoHistoryItem([
{ description: 'Older Task 1', status: 'completed' },
@@ -128,10 +140,11 @@ describe.each([true, false])(
showFullTodos,
});
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders full list when all todos are inactive', () => {
const { lastFrame } = renderWithUiState({
it('renders full list when all todos are inactive', async () => {
const { lastFrame, unmount } = await renderWithUiState({
history: [
createTodoHistoryItem([
{ description: 'Task 1', status: 'completed' },
@@ -140,7 +153,8 @@ describe.each([true, false])(
],
showFullTodos,
});
expect(lastFrame()).toMatchSnapshot();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
},
);
@@ -38,7 +38,7 @@ describe('ToolConfirmationMessage', () => {
getIdeMode: () => false,
} as unknown as Config;
it('should not display urls if prompt and url are the same', () => {
it('should not display urls if prompt and url are the same', async () => {
const confirmationDetails: SerializableConfirmationDetails = {
type: 'info',
title: 'Confirm Web Fetch',
@@ -46,7 +46,7 @@ describe('ToolConfirmationMessage', () => {
urls: ['https://example.com'],
};
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={confirmationDetails}
@@ -55,11 +55,13 @@ describe('ToolConfirmationMessage', () => {
terminalWidth={80}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('should display urls if prompt and url are different', () => {
it('should display urls if prompt and url are different', async () => {
const confirmationDetails: SerializableConfirmationDetails = {
type: 'info',
title: 'Confirm Web Fetch',
@@ -70,7 +72,7 @@ describe('ToolConfirmationMessage', () => {
],
};
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={confirmationDetails}
@@ -79,11 +81,13 @@ describe('ToolConfirmationMessage', () => {
terminalWidth={80}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('should display multiple commands for exec type when provided', () => {
it('should display multiple commands for exec type when provided', async () => {
const confirmationDetails: SerializableConfirmationDetails = {
type: 'exec',
title: 'Confirm Multiple Commands',
@@ -93,7 +97,7 @@ describe('ToolConfirmationMessage', () => {
commands: ['echo "hello"', 'ls -la', 'whoami'], // Multi-command list
};
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={confirmationDetails}
@@ -102,12 +106,14 @@ describe('ToolConfirmationMessage', () => {
terminalWidth={80}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('echo "hello"');
expect(output).toContain('ls -la');
expect(output).toContain('whoami');
expect(output).toMatchSnapshot();
unmount();
});
describe('with folder trust', () => {
@@ -166,13 +172,13 @@ describe('ToolConfirmationMessage', () => {
alwaysAllowText: 'always allow',
},
])('$description', ({ details }) => {
it('should show "allow always" when folder is trusted', () => {
it('should show "allow always" when folder is trusted', async () => {
const mockConfig = {
isTrustedFolder: () => true,
getIdeMode: () => false,
} as unknown as Config;
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={details}
@@ -181,17 +187,19 @@ describe('ToolConfirmationMessage', () => {
terminalWidth={80}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('should NOT show "allow always" when folder is untrusted', () => {
it('should NOT show "allow always" when folder is untrusted', async () => {
const mockConfig = {
isTrustedFolder: () => false,
getIdeMode: () => false,
} as unknown as Config;
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={details}
@@ -200,8 +208,10 @@ describe('ToolConfirmationMessage', () => {
terminalWidth={80}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
});
});
@@ -217,13 +227,13 @@ describe('ToolConfirmationMessage', () => {
newContent: 'b',
};
it('should NOT show "Allow for all future sessions" when setting is false (default)', () => {
it('should NOT show "Allow for all future sessions" when setting is false (default)', async () => {
const mockConfig = {
isTrustedFolder: () => true,
getIdeMode: () => false,
} as unknown as Config;
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={editConfirmationDetails}
@@ -237,17 +247,19 @@ describe('ToolConfirmationMessage', () => {
}),
},
);
await waitUntilReady();
expect(lastFrame()).not.toContain('Allow for all future sessions');
unmount();
});
it('should show "Allow for all future sessions" when setting is true', () => {
it('should show "Allow for all future sessions" when setting is true', async () => {
const mockConfig = {
isTrustedFolder: () => true,
getIdeMode: () => false,
} as unknown as Config;
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={editConfirmationDetails}
@@ -261,8 +273,10 @@ describe('ToolConfirmationMessage', () => {
}),
},
);
await waitUntilReady();
expect(lastFrame()).toContain('Allow for all future sessions');
unmount();
});
});
@@ -277,7 +291,7 @@ describe('ToolConfirmationMessage', () => {
newContent: 'b',
};
it('should show "Modify with external editor" when NOT in IDE mode', () => {
it('should show "Modify with external editor" when NOT in IDE mode', async () => {
const mockConfig = {
isTrustedFolder: () => true,
getIdeMode: () => false,
@@ -289,7 +303,7 @@ describe('ToolConfirmationMessage', () => {
isDiffingEnabled: false,
});
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={editConfirmationDetails}
@@ -298,11 +312,13 @@ describe('ToolConfirmationMessage', () => {
terminalWidth={80}
/>,
);
await waitUntilReady();
expect(lastFrame()).toContain('Modify with external editor');
unmount();
});
it('should show "Modify with external editor" when in IDE mode but diffing is NOT enabled', () => {
it('should show "Modify with external editor" when in IDE mode but diffing is NOT enabled', async () => {
const mockConfig = {
isTrustedFolder: () => true,
getIdeMode: () => true,
@@ -314,7 +330,7 @@ describe('ToolConfirmationMessage', () => {
isDiffingEnabled: false,
});
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={editConfirmationDetails}
@@ -323,11 +339,13 @@ describe('ToolConfirmationMessage', () => {
terminalWidth={80}
/>,
);
await waitUntilReady();
expect(lastFrame()).toContain('Modify with external editor');
unmount();
});
it('should NOT show "Modify with external editor" when in IDE mode AND diffing is enabled', () => {
it('should NOT show "Modify with external editor" when in IDE mode AND diffing is enabled', async () => {
const mockConfig = {
isTrustedFolder: () => true,
getIdeMode: () => true,
@@ -339,7 +357,7 @@ describe('ToolConfirmationMessage', () => {
isDiffingEnabled: true,
});
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolConfirmationMessage
callId="test-call-id"
confirmationDetails={editConfirmationDetails}
@@ -348,8 +366,10 @@ describe('ToolConfirmationMessage', () => {
terminalWidth={80}
/>,
);
await waitUntilReady();
expect(lastFrame()).not.toContain('Modify with external editor');
unmount();
});
});
});
@@ -65,10 +65,10 @@ describe('<ToolGroupMessage />', () => {
});
describe('Golden Snapshots', () => {
it('renders single successful tool call', () => {
it('renders single successful tool call', async () => {
const toolCalls = [createToolCall()];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
{
config: baseMockConfig,
@@ -82,11 +82,12 @@ describe('<ToolGroupMessage />', () => {
},
},
);
expect(lastFrame()).toMatchSnapshot();
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
it('hides confirming tools (standard behavior)', () => {
it('hides confirming tools (standard behavior)', async () => {
const toolCalls = [
createToolCall({
callId: 'confirm-tool',
@@ -100,17 +101,18 @@ describe('<ToolGroupMessage />', () => {
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
{ config: baseMockConfig },
);
// Should render nothing because all tools in the group are confirming
expect(lastFrame()).toBe('');
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('renders multiple tool calls with different statuses (only visible ones)', () => {
it('renders multiple tool calls with different statuses (only visible ones)', async () => {
const toolCalls = [
createToolCall({
callId: 'tool-1',
@@ -133,7 +135,7 @@ describe('<ToolGroupMessage />', () => {
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
{
config: baseMockConfig,
@@ -148,6 +150,7 @@ describe('<ToolGroupMessage />', () => {
},
);
// pending-tool should be hidden
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('successful-tool');
expect(output).not.toContain('pending-tool');
@@ -156,7 +159,7 @@ describe('<ToolGroupMessage />', () => {
unmount();
});
it('renders mixed tool calls including shell command', () => {
it('renders mixed tool calls including shell command', async () => {
const toolCalls = [
createToolCall({
callId: 'tool-1',
@@ -179,7 +182,7 @@ describe('<ToolGroupMessage />', () => {
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
{
config: baseMockConfig,
@@ -194,6 +197,7 @@ describe('<ToolGroupMessage />', () => {
},
);
// write_file (Pending) should be hidden
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('read_file');
expect(output).toContain('run_shell_command');
@@ -202,7 +206,7 @@ describe('<ToolGroupMessage />', () => {
unmount();
});
it('renders with limited terminal height', () => {
it('renders with limited terminal height', async () => {
const toolCalls = [
createToolCall({
callId: 'tool-1',
@@ -219,7 +223,7 @@ describe('<ToolGroupMessage />', () => {
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage
{...baseProps}
item={item}
@@ -238,11 +242,12 @@ describe('<ToolGroupMessage />', () => {
},
},
);
expect(lastFrame()).toMatchSnapshot();
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
it('renders with narrow terminal width', () => {
it('renders with narrow terminal width', async () => {
const toolCalls = [
createToolCall({
name: 'very-long-tool-name-that-might-wrap',
@@ -251,7 +256,7 @@ describe('<ToolGroupMessage />', () => {
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage
{...baseProps}
item={item}
@@ -270,14 +275,15 @@ describe('<ToolGroupMessage />', () => {
},
},
);
expect(lastFrame()).toMatchSnapshot();
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
it('renders empty tool calls array', () => {
it('renders empty tool calls array', async () => {
const toolCalls: IndividualToolCallDisplay[] = [];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
{
config: baseMockConfig,
@@ -291,11 +297,12 @@ describe('<ToolGroupMessage />', () => {
},
},
);
expect(lastFrame()).toMatchSnapshot();
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
it('renders header when scrolled', () => {
it('renders header when scrolled', async () => {
const toolCalls = [
createToolCall({
callId: '1',
@@ -312,7 +319,7 @@ describe('<ToolGroupMessage />', () => {
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<Scrollable height={10} hasFocus={true} scrollToBottom={true}>
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />
</Scrollable>,
@@ -328,11 +335,12 @@ describe('<ToolGroupMessage />', () => {
},
},
);
expect(lastFrame()).toMatchSnapshot();
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
it('renders tool call with outputFile', () => {
it('renders tool call with outputFile', async () => {
const toolCalls = [
createToolCall({
callId: 'tool-output-file',
@@ -343,7 +351,7 @@ describe('<ToolGroupMessage />', () => {
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
{
config: baseMockConfig,
@@ -357,11 +365,12 @@ describe('<ToolGroupMessage />', () => {
},
},
);
expect(lastFrame()).toMatchSnapshot();
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
it('renders two tool groups where only the last line of the previous group is visible', () => {
it('renders two tool groups where only the last line of the previous group is visible', async () => {
const toolCalls1 = [
createToolCall({
callId: '1',
@@ -381,7 +390,7 @@ describe('<ToolGroupMessage />', () => {
];
const item2 = createItem(toolCalls2);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<Scrollable height={6} hasFocus={true} scrollToBottom={true}>
<ToolGroupMessage
{...baseProps}
@@ -410,13 +419,14 @@ describe('<ToolGroupMessage />', () => {
},
},
);
expect(lastFrame()).toMatchSnapshot();
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
});
describe('Border Color Logic', () => {
it('uses yellow border for shell commands even when successful', () => {
it('uses yellow border for shell commands even when successful', async () => {
const toolCalls = [
createToolCall({
name: 'run_shell_command',
@@ -424,7 +434,7 @@ describe('<ToolGroupMessage />', () => {
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
{
config: baseMockConfig,
@@ -438,11 +448,12 @@ describe('<ToolGroupMessage />', () => {
},
},
);
expect(lastFrame()).toMatchSnapshot();
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
it('uses gray border when all tools are successful and no shell commands', () => {
it('uses gray border when all tools are successful and no shell commands', async () => {
const toolCalls = [
createToolCall({ status: CoreToolCallStatus.Success }),
createToolCall({
@@ -452,7 +463,7 @@ describe('<ToolGroupMessage />', () => {
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
{
config: baseMockConfig,
@@ -466,13 +477,14 @@ describe('<ToolGroupMessage />', () => {
},
},
);
expect(lastFrame()).toMatchSnapshot();
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
});
describe('Height Calculation', () => {
it('calculates available height correctly with multiple tools with results', () => {
it('calculates available height correctly with multiple tools with results', async () => {
const toolCalls = [
createToolCall({
callId: 'tool-1',
@@ -488,7 +500,7 @@ describe('<ToolGroupMessage />', () => {
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage
{...baseProps}
item={item}
@@ -507,7 +519,8 @@ describe('<ToolGroupMessage />', () => {
},
},
);
expect(lastFrame()).toMatchSnapshot();
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
});
@@ -542,7 +555,7 @@ describe('<ToolGroupMessage />', () => {
},
])(
'filtering logic for status=$status and hasResult=$resultDisplay',
({ status, resultDisplay, shouldHide }) => {
async ({ status, resultDisplay, shouldHide }) => {
const toolCalls = [
createToolCall({
callId: `ask-user-${status}`,
@@ -553,13 +566,14 @@ describe('<ToolGroupMessage />', () => {
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
{ config: baseMockConfig },
);
await waitUntilReady();
if (shouldHide) {
expect(lastFrame()).toBe('');
expect(lastFrame({ allowEmpty: true })).toBe('');
} else {
expect(lastFrame()).toMatchSnapshot();
}
@@ -567,7 +581,7 @@ describe('<ToolGroupMessage />', () => {
},
);
it('shows other tools when ask_user is filtered out', () => {
it('shows other tools when ask_user is filtered out', async () => {
const toolCalls = [
createToolCall({
callId: 'other-tool',
@@ -582,16 +596,17 @@ describe('<ToolGroupMessage />', () => {
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
{ config: baseMockConfig },
);
expect(lastFrame()).toMatchSnapshot();
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
it('renders nothing when only tool is in-progress AskUser with borderBottom=false', () => {
it('renders nothing when only tool is in-progress AskUser with borderBottom=false', async () => {
// AskUser tools in progress are rendered by AskUserDialog, not ToolGroupMessage.
// When AskUser is the only tool and borderBottom=false (no border to close),
// the component should render nothing.
@@ -604,7 +619,7 @@ describe('<ToolGroupMessage />', () => {
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage
{...baseProps}
item={item}
@@ -614,7 +629,8 @@ describe('<ToolGroupMessage />', () => {
{ config: baseMockConfig },
);
// AskUser tools in progress are rendered by AskUserDialog, so we expect nothing.
expect(lastFrame()).toBe('');
await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
});
@@ -634,27 +650,32 @@ describe('<ToolGroupMessage />', () => {
},
{ name: READ_FILE_DISPLAY_NAME, mode: ApprovalMode.PLAN, visible: true },
{ name: GLOB_DISPLAY_NAME, mode: ApprovalMode.PLAN, visible: true },
])('filtering logic for $name in $mode mode', ({ name, mode, visible }) => {
const toolCalls = [
createToolCall({
callId: 'test-call',
name,
approvalMode: mode,
}),
];
const item = createItem(toolCalls);
])(
'filtering logic for $name in $mode mode',
async ({ name, mode, visible }) => {
const toolCalls = [
createToolCall({
callId: 'test-call',
name,
approvalMode: mode,
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = renderWithProviders(
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
{ config: baseMockConfig },
);
const { lastFrame, unmount, waitUntilReady } = renderWithProviders(
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
{ config: baseMockConfig },
);
if (visible) {
expect(lastFrame()).toContain(name);
} else {
expect(lastFrame()).toBe('');
}
unmount();
});
await waitUntilReady();
if (visible) {
expect(lastFrame()).toContain(name);
} else {
expect(lastFrame({ allowEmpty: true })).toBe('');
}
unmount();
},
);
});
});
@@ -59,25 +59,33 @@ describe('<ToolMessage />', () => {
renderWithProviders(ui, {
uiActions,
uiState: { streamingState },
width: 80,
});
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
});
it('renders basic tool information', () => {
const { lastFrame } = renderWithContext(
afterEach(() => {
vi.useRealTimers();
});
it('renders basic tool information', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage {...baseProps} />,
StreamingState.Idle,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
describe('JSON rendering', () => {
it('pretty prints valid JSON', () => {
it('pretty prints valid JSON', async () => {
const testJSONstring = '{"a": 1, "b": [2, 3]}';
const { lastFrame } = renderWithContext(
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage
{...baseProps}
resultDisplay={testJSONstring}
@@ -85,6 +93,7 @@ describe('<ToolMessage />', () => {
/>,
StreamingState.Idle,
);
await waitUntilReady();
const output = lastFrame();
@@ -94,22 +103,25 @@ describe('<ToolMessage />', () => {
expect(output).toContain('"a": 1');
expect(output).toContain('"b": [');
// Should not use markdown renderer for JSON
unmount();
});
it('renders pretty JSON in ink frame', () => {
const { lastFrame } = renderWithContext(
it('renders pretty JSON in ink frame', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage {...baseProps} resultDisplay='{"a":1,"b":2}' />,
StreamingState.Idle,
);
await waitUntilReady();
const frame = lastFrame();
expect(frame).toMatchSnapshot();
unmount();
});
it('uses JSON renderer even when renderOutputAsMarkdown=true is true', () => {
it('uses JSON renderer even when renderOutputAsMarkdown=true is true', async () => {
const testJSONstring = '{"a": 1, "b": [2, 3]}';
const { lastFrame } = renderWithContext(
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage
{...baseProps}
resultDisplay={testJSONstring}
@@ -117,6 +129,7 @@ describe('<ToolMessage />', () => {
/>,
StreamingState.Idle,
);
await waitUntilReady();
const output = lastFrame();
@@ -126,10 +139,11 @@ describe('<ToolMessage />', () => {
expect(output).toContain('"a": 1');
expect(output).toContain('"b": [');
// Should not use markdown renderer for JSON even when renderOutputAsMarkdown=true
unmount();
});
it('falls back to plain text for malformed JSON', () => {
it('falls back to plain text for malformed JSON', async () => {
const testJSONstring = 'a": 1, "b": [2, 3]}';
const { lastFrame } = renderWithContext(
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage
{...baseProps}
resultDisplay={testJSONstring}
@@ -137,16 +151,18 @@ describe('<ToolMessage />', () => {
/>,
StreamingState.Idle,
);
await waitUntilReady();
const output = lastFrame();
expect(tryParseJSON(testJSONstring)).toBeFalsy();
expect(typeof output === 'string').toBeTruthy();
unmount();
});
it('rejects mixed text + JSON renders as plain text', () => {
it('rejects mixed text + JSON renders as plain text', async () => {
const testJSONstring = `{"result": "count": 42,"items": ["apple", "banana"]},"meta": {"timestamp": "2025-09-28T12:34:56Z"}}End.`;
const { lastFrame } = renderWithContext(
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage
{...baseProps}
resultDisplay={testJSONstring}
@@ -154,17 +170,19 @@ describe('<ToolMessage />', () => {
/>,
StreamingState.Idle,
);
await waitUntilReady();
const output = lastFrame();
expect(tryParseJSON(testJSONstring)).toBeFalsy();
expect(typeof output === 'string').toBeTruthy();
unmount();
});
it('rejects ANSI-tained JSON renders as plain text', () => {
it('rejects ANSI-tained JSON renders as plain text', async () => {
const testJSONstring =
'\u001b[32mOK\u001b[0m {"status": "success", "data": {"id": 123, "values": [10, 20, 30]}}';
const { lastFrame } = renderWithContext(
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage
{...baseProps}
resultDisplay={testJSONstring}
@@ -172,16 +190,18 @@ describe('<ToolMessage />', () => {
/>,
StreamingState.Idle,
);
await waitUntilReady();
const output = lastFrame();
expect(tryParseJSON(testJSONstring)).toBeFalsy();
expect(typeof output === 'string').toBeTruthy();
unmount();
});
it('pretty printing 10kb JSON completes in <50ms', () => {
it('pretty printing 10kb JSON completes in <50ms', async () => {
const large = '{"key": "' + 'x'.repeat(10000) + '"}';
const { lastFrame } = renderWithContext(
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage
{...baseProps}
resultDisplay={large}
@@ -189,83 +209,101 @@ describe('<ToolMessage />', () => {
/>,
StreamingState.Idle,
);
await waitUntilReady();
const start = performance.now();
lastFrame();
expect(performance.now() - start).toBeLessThan(50);
unmount();
});
});
describe('ToolStatusIndicator rendering', () => {
it('shows ✓ for Success status', () => {
const { lastFrame } = renderWithContext(
it('shows ✓ for Success status', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage {...baseProps} status={CoreToolCallStatus.Success} />,
StreamingState.Idle,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows o for Pending status', () => {
const { lastFrame } = renderWithContext(
it('shows o for Pending status', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage {...baseProps} status={CoreToolCallStatus.Scheduled} />,
StreamingState.Idle,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows ? for Confirming status', () => {
const { lastFrame } = renderWithContext(
it('shows ? for Confirming status', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage
{...baseProps}
status={CoreToolCallStatus.AwaitingApproval}
/>,
StreamingState.Idle,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows - for Canceled status', () => {
const { lastFrame } = renderWithContext(
it('shows - for Canceled status', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage {...baseProps} status={CoreToolCallStatus.Cancelled} />,
StreamingState.Idle,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows x for Error status', () => {
const { lastFrame } = renderWithContext(
it('shows x for Error status', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage {...baseProps} status={CoreToolCallStatus.Error} />,
StreamingState.Idle,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows paused spinner for Executing status when streamingState is Idle', () => {
const { lastFrame } = renderWithContext(
it('shows paused spinner for Executing status when streamingState is Idle', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage {...baseProps} status={CoreToolCallStatus.Executing} />,
StreamingState.Idle,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows paused spinner for Executing status when streamingState is WaitingForConfirmation', () => {
const { lastFrame } = renderWithContext(
it('shows paused spinner for Executing status when streamingState is WaitingForConfirmation', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage {...baseProps} status={CoreToolCallStatus.Executing} />,
StreamingState.WaitingForConfirmation,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows MockRespondingSpinner for Executing status when streamingState is Responding', () => {
const { lastFrame } = renderWithContext(
it('shows MockRespondingSpinner for Executing status when streamingState is Responding', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage {...baseProps} status={CoreToolCallStatus.Executing} />,
StreamingState.Responding, // Simulate app still responding
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
});
it('renders DiffRenderer for diff results', () => {
it('renders DiffRenderer for diff results', async () => {
const diffResult = {
fileDiff: '--- a/file.txt\n+++ b/file.txt\n@@ -1 +1 @@\n-old\n+new',
fileName: 'file.txt',
@@ -273,33 +311,47 @@ describe('<ToolMessage />', () => {
newContent: 'new',
filePath: 'file.txt',
};
const { lastFrame } = renderWithContext(
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage {...baseProps} resultDisplay={diffResult} />,
StreamingState.Idle,
);
await waitUntilReady();
// Check that the output contains the MockDiff content as part of the whole message
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders emphasis correctly', () => {
const { lastFrame: highEmphasisFrame } = renderWithContext(
it('renders emphasis correctly', async () => {
const {
lastFrame: highEmphasisFrame,
waitUntilReady: waitUntilReadyHigh,
unmount: unmountHigh,
} = renderWithContext(
<ToolMessage {...baseProps} emphasis="high" />,
StreamingState.Idle,
);
await waitUntilReadyHigh();
// Check for trailing indicator or specific color if applicable (Colors are not easily testable here)
expect(highEmphasisFrame()).toMatchSnapshot();
unmountHigh();
const { lastFrame: lowEmphasisFrame } = renderWithContext(
const {
lastFrame: lowEmphasisFrame,
waitUntilReady: waitUntilReadyLow,
unmount: unmountLow,
} = renderWithContext(
<ToolMessage {...baseProps} emphasis="low" />,
StreamingState.Idle,
);
await waitUntilReadyLow();
// For low emphasis, the name and description might be dimmed (check for dimColor if possible)
// This is harder to assert directly in text output without color checks.
// We can at least ensure it doesn't have the high emphasis indicator.
expect(lowEmphasisFrame()).toMatchSnapshot();
unmountLow();
});
it('renders AnsiOutputText for AnsiOutput results', () => {
it('renders AnsiOutputText for AnsiOutput results', async () => {
const ansiResult: AnsiOutput = [
[
{
@@ -314,15 +366,17 @@ describe('<ToolMessage />', () => {
},
],
];
const { lastFrame } = renderWithContext(
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage {...baseProps} resultDisplay={ansiResult} />,
StreamingState.Idle,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders progress information appended to description for executing tools', () => {
const { lastFrame } = renderWithContext(
it('renders progress information appended to description for executing tools', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage
{...baseProps}
status={CoreToolCallStatus.Executing}
@@ -331,13 +385,15 @@ describe('<ToolMessage />', () => {
/>,
StreamingState.Responding,
);
await waitUntilReady();
expect(lastFrame()).toContain(
'A tool for testing (Working on it... - 42%)',
);
unmount();
});
it('renders only percentage when progressMessage is missing', () => {
const { lastFrame } = renderWithContext(
it('renders only percentage when progressMessage is missing', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithContext(
<ToolMessage
{...baseProps}
status={CoreToolCallStatus.Executing}
@@ -345,6 +401,8 @@ describe('<ToolMessage />', () => {
/>,
StreamingState.Responding,
);
await waitUntilReady();
expect(lastFrame()).toContain('A tool for testing (75%)');
unmount();
});
});
@@ -66,46 +66,52 @@ describe('Focus Hint', () => {
describe.each(testCases)('$componentName', ({ Component }) => {
it('shows focus hint after delay even with NO output', async () => {
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<Component {...baseProps} resultDisplay={undefined} />,
{ uiState: { streamingState: StreamingState.Idle } },
);
await waitUntilReady();
// Initially, no focus hint
expect(lastFrame()).toMatchSnapshot('initial-no-output');
// Advance timers by the delay
act(() => {
await act(async () => {
vi.advanceTimersByTime(SHELL_FOCUS_HINT_DELAY_MS + 100);
});
await waitUntilReady();
// Now it SHOULD contain the focus hint
expect(lastFrame()).toMatchSnapshot('after-delay-no-output');
expect(lastFrame()).toContain('(Tab to focus)');
unmount();
});
it('shows focus hint after delay with output', async () => {
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<Component {...baseProps} resultDisplay="Some output" />,
{ uiState: { streamingState: StreamingState.Idle } },
);
await waitUntilReady();
// Initially, no focus hint
expect(lastFrame()).toMatchSnapshot('initial-with-output');
// Advance timers
act(() => {
await act(async () => {
vi.advanceTimersByTime(SHELL_FOCUS_HINT_DELAY_MS + 100);
});
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot('after-delay-with-output');
expect(lastFrame()).toContain('(Tab to focus)');
unmount();
});
});
it('handles long descriptions by shrinking them to show the focus hint', async () => {
const longDescription = 'A'.repeat(100);
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolMessage
{...baseProps}
description={longDescription}
@@ -113,15 +119,18 @@ describe('Focus Hint', () => {
/>,
{ uiState: { streamingState: StreamingState.Idle } },
);
await waitUntilReady();
act(() => {
await act(async () => {
vi.advanceTimersByTime(SHELL_FOCUS_HINT_DELAY_MS + 100);
});
await waitUntilReady();
// The focus hint should be visible
expect(lastFrame()).toMatchSnapshot('long-description');
expect(lastFrame()).toContain('(Tab to focus)');
// The name should still be visible
expect(lastFrame()).toContain(SHELL_COMMAND_NAME);
unmount();
});
});
@@ -63,8 +63,8 @@ describe('<ToolMessage /> - Raw Markdown Display Snapshots', () => {
},
])(
'renders with renderMarkdown=$renderMarkdown, useAlternateBuffer=$useAlternateBuffer $description',
({ renderMarkdown, useAlternateBuffer, availableTerminalHeight }) => {
const { lastFrame } = renderWithProviders(
async ({ renderMarkdown, useAlternateBuffer, availableTerminalHeight }) => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<StreamingContext.Provider value={StreamingState.Idle}>
<ToolMessage
{...baseProps}
@@ -76,7 +76,9 @@ describe('<ToolMessage /> - Raw Markdown Display Snapshots', () => {
useAlternateBuffer,
},
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
},
);
});
@@ -33,10 +33,7 @@ describe('ToolResultDisplay', () => {
mockUseAlternateBuffer.mockReturnValue(false);
});
// Helper to use renderWithProviders
const render = (ui: React.ReactElement) => renderWithProviders(ui);
it('uses ScrollableList for ANSI output in alternate buffer mode', () => {
it('uses ScrollableList for ANSI output in alternate buffer mode', async () => {
mockUseAlternateBuffer.mockReturnValue(true);
const content = 'ansi content';
const ansiResult: AnsiOutput = [
@@ -53,57 +50,65 @@ describe('ToolResultDisplay', () => {
},
],
];
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay
resultDisplay={ansiResult}
terminalWidth={80}
maxLines={10}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain(content);
unmount();
});
it('uses Scrollable for non-ANSI output in alternate buffer mode', () => {
it('uses Scrollable for non-ANSI output in alternate buffer mode', async () => {
mockUseAlternateBuffer.mockReturnValue(true);
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay
resultDisplay="**Markdown content**"
terminalWidth={80}
maxLines={10}
/>,
);
await waitUntilReady();
const output = lastFrame();
// With real components, we check for the content itself
expect(output).toContain('Markdown content');
unmount();
});
it('passes hasFocus prop to scrollable components', () => {
it('passes hasFocus prop to scrollable components', async () => {
mockUseAlternateBuffer.mockReturnValue(true);
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay
resultDisplay="Some result"
terminalWidth={80}
hasFocus={true}
/>,
);
await waitUntilReady();
expect(lastFrame()).toContain('Some result');
unmount();
});
it('renders string result as markdown by default', () => {
const { lastFrame } = render(
it('renders string result as markdown by default', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay resultDisplay="**Some result**" terminalWidth={80} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('renders string result as plain text when renderOutputAsMarkdown is false', () => {
const { lastFrame } = render(
it('renders string result as plain text when renderOutputAsMarkdown is false', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay
resultDisplay="**Some result**"
terminalWidth={80}
@@ -111,43 +116,49 @@ describe('ToolResultDisplay', () => {
renderOutputAsMarkdown={false}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('truncates very long string results', { timeout: 20000 }, () => {
it('truncates very long string results', { timeout: 20000 }, async () => {
const longString = 'a'.repeat(1000005);
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay
resultDisplay={longString}
terminalWidth={80}
availableTerminalHeight={20}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('renders file diff result', () => {
it('renders file diff result', async () => {
const diffResult = {
fileDiff: 'diff content',
fileName: 'test.ts',
};
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay
resultDisplay={diffResult}
terminalWidth={80}
availableTerminalHeight={20}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('renders ANSI output result', () => {
it('renders ANSI output result', async () => {
const ansiResult: AnsiOutput = [
[
{
@@ -162,38 +173,42 @@ describe('ToolResultDisplay', () => {
},
],
];
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay
resultDisplay={ansiResult as unknown as AnsiOutput}
terminalWidth={80}
availableTerminalHeight={20}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('renders nothing for todos result', () => {
it('renders nothing for todos result', async () => {
const todoResult = {
todos: [],
};
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay
resultDisplay={todoResult}
terminalWidth={80}
availableTerminalHeight={20}
/>,
);
const output = lastFrame();
await waitUntilReady();
const output = lastFrame({ allowEmpty: true });
expect(output).toMatchSnapshot();
unmount();
});
it('does not fall back to plain text if availableHeight is set and not in alternate buffer', () => {
it('does not fall back to plain text if availableHeight is set and not in alternate buffer', async () => {
mockUseAlternateBuffer.mockReturnValue(false);
// availableHeight calculation: 20 - 1 - 5 = 14 > 3
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay
resultDisplay="**Some result**"
terminalWidth={80}
@@ -201,13 +216,15 @@ describe('ToolResultDisplay', () => {
renderOutputAsMarkdown={true}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('keeps markdown if in alternate buffer even with availableHeight', () => {
it('keeps markdown if in alternate buffer even with availableHeight', async () => {
mockUseAlternateBuffer.mockReturnValue(true);
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay
resultDisplay="**Some result**"
terminalWidth={80}
@@ -215,12 +232,14 @@ describe('ToolResultDisplay', () => {
renderOutputAsMarkdown={true}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('truncates ANSI output when maxLines is provided', () => {
it('truncates ANSI output when maxLines is provided', async () => {
const ansiResult: AnsiOutput = [
[
{
@@ -259,7 +278,7 @@ describe('ToolResultDisplay', () => {
},
],
];
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay
resultDisplay={ansiResult}
terminalWidth={80}
@@ -267,14 +286,16 @@ describe('ToolResultDisplay', () => {
maxLines={2}
/>,
);
await waitUntilReady();
const output = lastFrame();
expect(output).not.toContain('Line 1');
expect(output).toContain('Line 2');
expect(output).toContain('Line 3');
unmount();
});
it('truncates ANSI output when maxLines is provided, even if availableTerminalHeight is undefined', () => {
it('truncates ANSI output when maxLines is provided, even if availableTerminalHeight is undefined', async () => {
const ansiResult: AnsiOutput = Array.from({ length: 50 }, (_, i) => [
{
text: `Line ${i + 1}`,
@@ -287,7 +308,7 @@ describe('ToolResultDisplay', () => {
inverse: false,
},
]);
const { lastFrame } = render(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<ToolResultDisplay
resultDisplay={ansiResult}
terminalWidth={80}
@@ -295,10 +316,12 @@ describe('ToolResultDisplay', () => {
availableTerminalHeight={undefined}
/>,
);
await waitUntilReady();
const output = lastFrame();
// It SHOULD truncate to 25 lines because maxLines is provided
expect(output).not.toContain('Line 1');
expect(output).toContain('Line 50');
unmount();
});
});
@@ -91,7 +91,7 @@ describe('ToolMessage Sticky Header Regression', () => {
);
};
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<Box height={terminalHeight}>
<TestComponent />
</Box>,
@@ -100,6 +100,7 @@ describe('ToolMessage Sticky Header Regression', () => {
uiState: { terminalWidth },
},
);
await waitUntilReady();
// Initial state: tool-1 should be visible
await waitFor(() => {
@@ -112,6 +113,7 @@ describe('ToolMessage Sticky Header Regression', () => {
await act(async () => {
listRef?.scrollBy(5);
});
await waitUntilReady();
// tool-1 header should still be visible because it is sticky
await waitFor(() => {
@@ -130,6 +132,7 @@ describe('ToolMessage Sticky Header Regression', () => {
await act(async () => {
listRef?.scrollBy(17);
});
await waitUntilReady();
await waitFor(() => {
expect(lastFrame()).toContain('tool-2');
@@ -138,6 +141,7 @@ describe('ToolMessage Sticky Header Regression', () => {
// tool-1 should be gone now (both header and content)
expect(lastFrame()).not.toContain('tool-1');
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('verifies that ShellToolMessage in a ToolGroupMessage in a ScrollableList has sticky headers', async () => {
@@ -177,7 +181,7 @@ describe('ToolMessage Sticky Header Regression', () => {
);
};
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<Box height={terminalHeight}>
<TestComponent />
</Box>,
@@ -186,6 +190,7 @@ describe('ToolMessage Sticky Header Regression', () => {
uiState: { terminalWidth },
},
);
await waitUntilReady();
await waitFor(() => {
expect(lastFrame()).toContain(SHELL_COMMAND_NAME);
@@ -196,11 +201,13 @@ describe('ToolMessage Sticky Header Regression', () => {
await act(async () => {
listRef?.scrollBy(5);
});
await waitUntilReady();
await waitFor(() => {
expect(lastFrame()).toContain(SHELL_COMMAND_NAME);
});
expect(lastFrame()).toContain('shell-06');
expect(lastFrame()).toMatchSnapshot();
unmount();
});
});
@@ -14,46 +14,54 @@ vi.mock('../../utils/commandUtils.js', () => ({
}));
describe('UserMessage', () => {
it('renders normal user message with correct prefix', () => {
const { lastFrame } = renderWithProviders(
it('renders normal user message with correct prefix', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<UserMessage text="Hello Gemini" width={80} />,
{ width: 80 },
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('renders slash command message', () => {
const { lastFrame } = renderWithProviders(
it('renders slash command message', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<UserMessage text="/help" width={80} />,
{ width: 80 },
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('renders multiline user message', () => {
it('renders multiline user message', async () => {
const message = 'Line 1\nLine 2';
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<UserMessage text={message} width={80} />,
{ width: 80 },
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('transforms image paths in user message', () => {
it('transforms image paths in user message', async () => {
const message = 'Check out this image: @/path/to/my-image.png';
const { lastFrame } = renderWithProviders(
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<UserMessage text={message} width={80} />,
{ width: 80 },
);
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('[Image my-image.png]');
expect(output).toMatchSnapshot();
unmount();
});
});
@@ -9,18 +9,26 @@ import { WarningMessage } from './WarningMessage.js';
import { describe, it, expect } from 'vitest';
describe('WarningMessage', () => {
it('renders with the correct prefix and text', () => {
const { lastFrame } = render(<WarningMessage text="Watch out!" />);
it('renders with the correct prefix and text', async () => {
const { lastFrame, waitUntilReady, unmount } = render(
<WarningMessage text="Watch out!" />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
it('renders multiline warning messages', () => {
it('renders multiline warning messages', async () => {
const message = 'Warning line 1\nWarning line 2';
const { lastFrame } = render(<WarningMessage text={message} />);
const { lastFrame, waitUntilReady, unmount } = render(
<WarningMessage text={message} />,
);
await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
});
});
@@ -5,7 +5,8 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
1 + const newVar = 1;
════════════════════════════════════════════════════════════════════════════════
20 - const anotherOld = 'test';
20 + const anotherNew = 'test';"
20 + const anotherNew = 'test';
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = false > should correctly render a diff with multiple hunks and a gap indicator > with terminalWidth 30 and height 6 1`] = `
@@ -14,7 +15,8 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
21 + const anotherNew =
'test';
22 console.log('end of second
hunk');"
hunk');
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = false > should correctly render a diff with multiple hunks and a gap indicator > with terminalWidth 80 and height 6 1`] = `
@@ -23,7 +25,8 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
20 console.log('second hunk');
21 - const anotherOld = 'test';
21 + const anotherNew = 'test';
22 console.log('end of second hunk');"
22 console.log('end of second hunk');
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = false > should correctly render a diff with multiple hunks and a gap indicator > with terminalWidth 80 and height undefined 1`] = `
@@ -35,24 +38,30 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
20 console.log('second hunk');
21 - const anotherOld = 'test';
21 + const anotherNew = 'test';
22 console.log('end of second hunk');"
22 console.log('end of second hunk');
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = false > should correctly render a new file with no file extension correctly 1`] = `
"1 FROM node:14
2 RUN npm install
3 RUN npm run build"
3 RUN npm run build
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = false > should handle diff with only header and no changes 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ No changes detected.
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ No changes detected. │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = false > should handle empty diff content 1`] = `"No diff content."`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = false > should handle empty diff content 1`] = `
"No diff content.
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = false > should not render a gap indicator for small gaps (<= MAX_CONTEXT_LINES_WITHOUT_GAP) 1`] = `
" 1 context line 1
@@ -64,7 +73,8 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
12 context line 12
13 context line 13
14 context line 14
15 context line 15"
15 context line 15
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = false > should render a gap indicator for skipped lines 1`] = `
@@ -73,12 +83,14 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
2 + added line
════════════════════════════════════════════════════════════════════════════════
10 context line 10
11 context line 11"
11 context line 11
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = false > should render diff content for existing file (not calling colorizeCode directly for the whole block) 1`] = `
"1 - old line
1 + new line"
1 + new line
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should correctly render a diff with a SVN diff format 1`] = `
@@ -86,7 +98,8 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
1 + const newVar = 1;
════════════════════════════════════════════════════════════════════════════════
20 - const anotherOld = 'test';
20 + const anotherNew = 'test';"
20 + const anotherNew = 'test';
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should correctly render a diff with multiple hunks and a gap indicator > with terminalWidth 30 and height 6 1`] = `
@@ -95,7 +108,8 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
21 + const anotherNew =
'test';
22 console.log('end of second
hunk');"
hunk');
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should correctly render a diff with multiple hunks and a gap indicator > with terminalWidth 80 and height 6 1`] = `
@@ -104,7 +118,8 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
20 console.log('second hunk');
21 - const anotherOld = 'test';
21 + const anotherNew = 'test';
22 console.log('end of second hunk');"
22 console.log('end of second hunk');
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should correctly render a diff with multiple hunks and a gap indicator > with terminalWidth 80 and height undefined 1`] = `
@@ -116,24 +131,30 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
20 console.log('second hunk');
21 - const anotherOld = 'test';
21 + const anotherNew = 'test';
22 console.log('end of second hunk');"
22 console.log('end of second hunk');
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should correctly render a new file with no file extension correctly 1`] = `
"1 FROM node:14
2 RUN npm install
3 RUN npm run build"
3 RUN npm run build
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should handle diff with only header and no changes 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ No changes detected.
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ No changes detected. │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should handle empty diff content 1`] = `"No diff content."`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should handle empty diff content 1`] = `
"No diff content.
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should not render a gap indicator for small gaps (<= MAX_CONTEXT_LINES_WITHOUT_GAP) 1`] = `
" 1 context line 1
@@ -145,7 +166,8 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
12 context line 12
13 context line 13
14 context line 14
15 context line 15"
15 context line 15
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should render a gap indicator for skipped lines 1`] = `
@@ -154,10 +176,12 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
2 + added line
════════════════════════════════════════════════════════════════════════════════
10 context line 10
11 context line 11"
11 context line 11
"
`;
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = true > should render diff content for existing file (not calling colorizeCode directly for the whole block) 1`] = `
"1 - old line
1 + new line"
1 + new line
"
`;
@@ -3,15 +3,18 @@
exports[`InfoMessage > renders multiline info messages 1`] = `
"
Info line 1
Info line 2"
Info line 2
"
`;
exports[`InfoMessage > renders with a custom icon 1`] = `
"
★Custom icon test"
★Custom icon test
"
`;
exports[`InfoMessage > renders with the correct default prefix and text 1`] = `
"
Just so you know"
Just so you know
"
`;
@@ -18,7 +18,8 @@ exports[`<ShellToolMessage /> > Height Constraints > defaults to ACTIVE_SHELL_MA
│ Line 97 │
│ Line 98 ▄ │
│ Line 99 █ │
│ Line 100 █ │"
│ Line 100 █ │
"
`;
exports[`<ShellToolMessage /> > Height Constraints > respects availableTerminalHeight when it is smaller than ACTIVE_SHELL_MAX_LINES 1`] = `
@@ -32,7 +33,8 @@ exports[`<ShellToolMessage /> > Height Constraints > respects availableTerminalH
│ Line 97 │
│ Line 98 │
│ Line 99 │
│ Line 100 █ │"
│ Line 100 █ │
"
`;
exports[`<ShellToolMessage /> > Height Constraints > uses ACTIVE_SHELL_MAX_LINES when availableTerminalHeight is large 1`] = `
@@ -53,7 +55,8 @@ exports[`<ShellToolMessage /> > Height Constraints > uses ACTIVE_SHELL_MAX_LINES
│ Line 97 │
│ Line 98 ▄ │
│ Line 99 █ │
│ Line 100 █ │"
│ Line 100 █ │
"
`;
exports[`<ShellToolMessage /> > Height Constraints > uses full availableTerminalHeight when focused in alternate buffer mode 1`] = `
@@ -158,7 +161,8 @@ exports[`<ShellToolMessage /> > Height Constraints > uses full availableTerminal
│ Line 98 █ │
│ Line 99 █ │
│ Line 100 █ │
│ │"
│ │
"
`;
exports[`<ShellToolMessage /> > Snapshots > renders in Alternate Buffer mode while focused 1`] = `
@@ -166,33 +170,38 @@ exports[`<ShellToolMessage /> > Snapshots > renders in Alternate Buffer mode whi
│ ⊷ Shell Command A shell command (Shift+Tab to unfocus) │
│ │
│ Test result │
│ │"
│ │
"
`;
exports[`<ShellToolMessage /> > Snapshots > renders in Alternate Buffer mode while unfocused 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ⊷ Shell Command A shell command │
│ │
│ Test result │"
│ Test result │
"
`;
exports[`<ShellToolMessage /> > Snapshots > renders in Error state 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ x Shell Command A shell command │
│ │
│ Error output │"
│ Error output │
"
`;
exports[`<ShellToolMessage /> > Snapshots > renders in Executing state 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ⊷ Shell Command A shell command │
│ │
│ Test result │"
│ Test result │
"
`;
exports[`<ShellToolMessage /> > Snapshots > renders in Success state (history mode) 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ Shell Command A shell command │
│ │
│ Test result │"
│ Test result │
"
`;
@@ -2,7 +2,8 @@
exports[`<TodoTray /> (showFullTodos: false) > renders a todo list with long descriptions that wrap when full view is on 1`] = `
"──────────────────────────────────────────────────
Todo 1/2 completed (ctrl+t to toggle) » This i…"
Todo 1/2 completed (ctrl+t to toggle) » This i…
"
`;
exports[`<TodoTray /> (showFullTodos: false) > renders full list when all todos are inactive 1`] = `""`;
@@ -13,17 +14,20 @@ exports[`<TodoTray /> (showFullTodos: false) > renders null when todo list is em
exports[`<TodoTray /> (showFullTodos: false) > renders the most recent todo list when multiple write_todos calls are in history 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
Todo 0/2 completed (ctrl+t to toggle) » Newer Task 2"
Todo 0/2 completed (ctrl+t to toggle) » Newer Task 2
"
`;
exports[`<TodoTray /> (showFullTodos: false) > renders when todos exist and one is in progress 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
Todo 1/3 completed (ctrl+t to toggle) » Task 2"
Todo 1/3 completed (ctrl+t to toggle) » Task 2
"
`;
exports[`<TodoTray /> (showFullTodos: false) > renders when todos exist but none are in progress 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
Todo 1/2 completed (ctrl+t to toggle)"
Todo 1/2 completed (ctrl+t to toggle)
"
`;
exports[`<TodoTray /> (showFullTodos: true) > renders a todo list with long descriptions that wrap when full view is on 1`] = `
@@ -34,7 +38,8 @@ exports[`<TodoTray /> (showFullTodos: true) > renders a todo list with long desc
task that should wrap around multiple lines
when the terminal width is constrained.
✓ Another completed task with an equally verbose
description to test wrapping behavior."
description to test wrapping behavior.
"
`;
exports[`<TodoTray /> (showFullTodos: true) > renders full list when all todos are inactive 1`] = `
@@ -42,7 +47,8 @@ exports[`<TodoTray /> (showFullTodos: true) > renders full list when all todos a
Todo 1/1 completed (ctrl+t to toggle)
✓ Task 1
✗ Task 2"
✗ Task 2
"
`;
exports[`<TodoTray /> (showFullTodos: true) > renders null when no todos are in the history 1`] = `""`;
@@ -54,7 +60,8 @@ exports[`<TodoTray /> (showFullTodos: true) > renders the most recent todo list
Todo 0/2 completed (ctrl+t to toggle)
☐ Newer Task 1
» Newer Task 2"
» Newer Task 2
"
`;
exports[`<TodoTray /> (showFullTodos: true) > renders when todos exist and one is in progress 1`] = `
@@ -64,7 +71,8 @@ exports[`<TodoTray /> (showFullTodos: true) > renders when todos exist and one i
☐ Pending Task
» Task 2
✗ In Progress Task
✓ Completed Task"
✓ Completed Task
"
`;
exports[`<TodoTray /> (showFullTodos: true) > renders when todos exist but none are in progress 1`] = `
@@ -73,5 +81,6 @@ exports[`<TodoTray /> (showFullTodos: true) > renders when todos exist but none
☐ Pending Task
✗ In Progress Task
✓ Completed Task"
✓ Completed Task
"
`;
@@ -55,13 +55,14 @@ exports[`<ToolGroupMessage /> > Golden Snapshots > renders header when scrolled
"╭──────────────────────────────────────────────────────────────────────────╮
│ ✓ tool-1 Description 1. This is a long description that will need to b… │
│──────────────────────────────────────────────────────────────────────────│
│ │
│ ✓ tool-2 Description 2 │
│ │
│ line1 │
│ line2 │
╰──────────────────────────────────────────────────────────────────────────╯
█"
│ │ ▄
│ ✓ tool-2 Description 2 │ █
│ │ █
│ line1 │ █
│ line2 │ █
╰──────────────────────────────────────────────────────────────────────────╯ █
"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders mixed tool calls including shell command 1`] = `
@@ -113,9 +114,10 @@ exports[`<ToolGroupMessage /> > Golden Snapshots > renders two tool groups where
"╭──────────────────────────────────────────────────────────────────────────╮
│ ✓ tool-2 Description 2 │
│ │
│ line1 │
╰──────────────────────────────────────────────────────────────────────────╯
█"
│ line1 │ ▄
╰──────────────────────────────────────────────────────────────────────────╯ █
"
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders with limited terminal height 1`] = `
@@ -7,70 +7,80 @@ exports[`<ToolMessage /> > JSON rendering > renders pretty JSON in ink frame 1`]
│ { │
│ "a": 1, │
│ "b": 2 │
│ } │"
│ } │
"
`;
exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows ? for Confirming status 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ? test-tool A tool for testing │
│ │
│ Test result │"
│ Test result │
"
`;
exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows - for Canceled status 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ - test-tool A tool for testing │
│ │
│ Test result │"
│ Test result │
"
`;
exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows MockRespondingSpinner for Executing status when streamingState is Responding 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ MockRespondingSpinnertest-tool A tool for testing │
│ │
│ Test result │"
│ Test result │
"
`;
exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows o for Pending status 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ o test-tool A tool for testing │
│ │
│ Test result │"
│ Test result │
"
`;
exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows paused spinner for Executing status when streamingState is Idle 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ MockRespondingSpinnertest-tool A tool for testing │
│ │
│ Test result │"
│ Test result │
"
`;
exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows paused spinner for Executing status when streamingState is WaitingForConfirmation 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ MockRespondingSpinnertest-tool A tool for testing │
│ │
│ Test result │"
│ Test result │
"
`;
exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows x for Error status 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ x test-tool A tool for testing │
│ │
│ Test result │"
│ Test result │
"
`;
exports[`<ToolMessage /> > ToolStatusIndicator rendering > shows ✓ for Success status 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test result │"
│ Test result │
"
`;
exports[`<ToolMessage /> > renders AnsiOutputText for AnsiOutput results 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ hello │"
│ hello │
"
`;
exports[`<ToolMessage /> > renders DiffRenderer for diff results 1`] = `
@@ -78,26 +88,30 @@ exports[`<ToolMessage /> > renders DiffRenderer for diff results 1`] = `
│ ✓ test-tool A tool for testing │
│ │
│ 1 - old │
│ 1 + new │"
│ 1 + new │
"
`;
exports[`<ToolMessage /> > renders basic tool information 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test result │"
│ Test result │
"
`;
exports[`<ToolMessage /> > renders emphasis correctly 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing ← │
│ │
│ Test result │"
│ Test result │
"
`;
exports[`<ToolMessage /> > renders emphasis correctly 2`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test result │"
│ Test result │
"
`;
@@ -3,53 +3,62 @@
exports[`Focus Hint > 'ShellToolMessage' > shows focus hint after delay even with NO output > after-delay-no-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Shell Command A tool for testing (Tab to focus) │
│ │"
│ │
"
`;
exports[`Focus Hint > 'ShellToolMessage' > shows focus hint after delay even with NO output > initial-no-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Shell Command A tool for testing │
│ │"
│ │
"
`;
exports[`Focus Hint > 'ShellToolMessage' > shows focus hint after delay with output > after-delay-with-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Shell Command A tool for testing (Tab to focus) │
│ │"
│ │
"
`;
exports[`Focus Hint > 'ShellToolMessage' > shows focus hint after delay with output > initial-with-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Shell Command A tool for testing │
│ │"
│ │
"
`;
exports[`Focus Hint > 'ToolMessage' > shows focus hint after delay even with NO output > after-delay-no-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Shell Command A tool for testing (Tab to focus) │
│ │"
│ │
"
`;
exports[`Focus Hint > 'ToolMessage' > shows focus hint after delay even with NO output > initial-no-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Shell Command A tool for testing │
│ │"
│ │
"
`;
exports[`Focus Hint > 'ToolMessage' > shows focus hint after delay with output > after-delay-with-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Shell Command A tool for testing (Tab to focus) │
│ │"
│ │
"
`;
exports[`Focus Hint > 'ToolMessage' > shows focus hint after delay with output > initial-with-output 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Shell Command A tool for testing │
│ │"
│ │
"
`;
exports[`Focus Hint > handles long descriptions by shrinking them to show the focus hint > long-description 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Shell Command AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA… (Tab to focus) │
│ │"
│ │
"
`;
@@ -4,40 +4,46 @@ exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderM
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test **bold** and \`code\` markdown │"
│ Test **bold** and \`code\` markdown │
"
`;
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=false, useAlternateBuffer=true '(raw markdown, alternate buffer)' 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test **bold** and \`code\` markdown │"
│ Test **bold** and \`code\` markdown │
"
`;
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=true, useAlternateBuffer=false '(constrained height, regular buffer -…' 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test bold and code markdown │"
│ Test bold and code markdown │
"
`;
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=true, useAlternateBuffer=false '(default, regular buffer)' 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test bold and code markdown │"
│ Test bold and code markdown │
"
`;
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=true, useAlternateBuffer=true '(constrained height, alternate buffer…' 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test bold and code markdown │"
│ Test bold and code markdown │
"
`;
exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=true, useAlternateBuffer=true '(default, alternate buffer)' 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
│ │
│ Test bold and code markdown │"
│ Test bold and code markdown │
"
`;
@@ -1,24 +1,40 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ToolResultDisplay > does not fall back to plain text if availableHeight is set and not in alternate buffer 1`] = `"Some result"`;
exports[`ToolResultDisplay > does not fall back to plain text if availableHeight is set and not in alternate buffer 1`] = `
"Some result
"
`;
exports[`ToolResultDisplay > keeps markdown if in alternate buffer even with availableHeight 1`] = `"Some result"`;
exports[`ToolResultDisplay > keeps markdown if in alternate buffer even with availableHeight 1`] = `
"Some result
"
`;
exports[`ToolResultDisplay > renders ANSI output result 1`] = `"ansi content"`;
exports[`ToolResultDisplay > renders ANSI output result 1`] = `
"ansi content
"
`;
exports[`ToolResultDisplay > renders file diff result 1`] = `
"╭──────────────────────────────────────────────────────────────────────────╮
│ │
│ No changes detected. │
│ │
╰──────────────────────────────────────────────────────────────────────────╯"
╰──────────────────────────────────────────────────────────────────────────╯
"
`;
exports[`ToolResultDisplay > renders nothing for todos result 1`] = `""`;
exports[`ToolResultDisplay > renders string result as markdown by default 1`] = `"Some result"`;
exports[`ToolResultDisplay > renders string result as markdown by default 1`] = `
"Some result
"
`;
exports[`ToolResultDisplay > renders string result as plain text when renderOutputAsMarkdown is false 1`] = `"**Some result**"`;
exports[`ToolResultDisplay > renders string result as plain text when renderOutputAsMarkdown is false 1`] = `
"**Some result**
"
`;
exports[`ToolResultDisplay > truncates very long string results 1`] = `
"... first 252 lines hidden ...
@@ -33,5 +49,6 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaa"
aaaaaaaaaaaaaaa
"
`;
@@ -5,7 +5,8 @@ exports[`ToolMessage Sticky Header Regression > verifies that ShellToolMessage i
│ ✓ Shell Command Description for Shell Command │ ▀
│ │
│ shell-01 │
│ shell-02 │"
│ shell-02 │
"
`;
exports[`ToolMessage Sticky Header Regression > verifies that ShellToolMessage in a ToolGroupMessage in a ScrollableList has sticky headers 2`] = `
@@ -13,7 +14,8 @@ exports[`ToolMessage Sticky Header Regression > verifies that ShellToolMessage i
│ ✓ Shell Command Description for Shell Command │
│────────────────────────────────────────────────────────────────────────│ █
│ shell-06 │ ▀
│ shell-07 │"
│ shell-07 │
"
`;
exports[`ToolMessage Sticky Header Regression > verifies that multiple ToolMessages in a ToolGroupMessage in a ScrollableList have sticky headers 1`] = `
@@ -21,7 +23,8 @@ exports[`ToolMessage Sticky Header Regression > verifies that multiple ToolMessa
│ ✓ tool-1 Description for tool-1 │
│ │
│ c1-01 │
│ c1-02 │"
│ c1-02 │
"
`;
exports[`ToolMessage Sticky Header Regression > verifies that multiple ToolMessages in a ToolGroupMessage in a ScrollableList have sticky headers 2`] = `
@@ -29,7 +32,8 @@ exports[`ToolMessage Sticky Header Regression > verifies that multiple ToolMessa
│ ✓ tool-1 Description for tool-1 │ █
│────────────────────────────────────────────────────────────────────────│
│ c1-06 │
│ c1-07 │"
│ c1-07 │
"
`;
exports[`ToolMessage Sticky Header Regression > verifies that multiple ToolMessages in a ToolGroupMessage in a ScrollableList have sticky headers 3`] = `
@@ -37,5 +41,6 @@ exports[`ToolMessage Sticky Header Regression > verifies that multiple ToolMessa
│ ✓ tool-2 Description for tool-2 │
│────────────────────────────────────────────────────────────────────────│
│ c2-10 │
╰────────────────────────────────────────────────────────────────────────╯ █"
╰────────────────────────────────────────────────────────────────────────╯ █
"
`;
@@ -4,23 +4,27 @@ exports[`UserMessage > renders multiline user message 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Line 1
Line 2
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄"
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`UserMessage > renders normal user message with correct prefix 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Hello Gemini
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄"
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`UserMessage > renders slash command message 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> /help
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄"
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`UserMessage > transforms image paths in user message 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Check out this image: [Image my-image.png]
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄"
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
@@ -3,10 +3,12 @@
exports[`WarningMessage > renders multiline warning messages 1`] = `
"
⚠ Warning line 1
Warning line 2"
Warning line 2
"
`;
exports[`WarningMessage > renders with the correct prefix and text 1`] = `
"
⚠ Watch out!"
⚠ Watch out!
"
`;