diff --git a/packages/cli/src/ui/components/InputPrompt.test.tsx b/packages/cli/src/ui/components/InputPrompt.test.tsx
index 0fe0a17eea..688f9a8538 100644
--- a/packages/cli/src/ui/components/InputPrompt.test.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.test.tsx
@@ -773,393 +773,96 @@ describe('InputPrompt', () => {
});
describe('cursor-based completion trigger', () => {
- it('should trigger completion when cursor is after @ without spaces', async () => {
- // Set up buffer state
- mockBuffer.text = '@src/components';
- mockBuffer.lines = ['@src/components'];
- mockBuffer.cursor = [0, 15];
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
+ it.each([
+ {
+ name: 'should trigger completion when cursor is after @ without spaces',
+ text: '@src/components',
+ cursor: [0, 15],
showSuggestions: true,
- suggestions: [{ label: 'Button.tsx', value: 'Button.tsx' }],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- // Verify useCompletion was called with correct signature
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should trigger completion when cursor is after / without spaces', async () => {
- mockBuffer.text = '/memory';
- mockBuffer.lines = ['/memory'];
- mockBuffer.cursor = [0, 7];
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
+ },
+ {
+ name: 'should trigger completion when cursor is after / without spaces',
+ text: '/memory',
+ cursor: [0, 7],
showSuggestions: true,
- suggestions: [{ label: 'show', value: 'show' }],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should NOT trigger completion when cursor is after space following @', async () => {
- mockBuffer.text = '@src/file.ts hello';
- mockBuffer.lines = ['@src/file.ts hello'];
- mockBuffer.cursor = [0, 18];
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
+ },
+ {
+ name: 'should NOT trigger completion when cursor is after space following @',
+ text: '@src/file.ts hello',
+ cursor: [0, 18],
showSuggestions: false,
- suggestions: [],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should NOT trigger completion when cursor is after space following /', async () => {
- mockBuffer.text = '/memory add';
- mockBuffer.lines = ['/memory add'];
- mockBuffer.cursor = [0, 11];
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
+ },
+ {
+ name: 'should NOT trigger completion when cursor is after space following /',
+ text: '/memory add',
+ cursor: [0, 11],
showSuggestions: false,
- suggestions: [],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should NOT trigger completion when cursor is not after @ or /', async () => {
- mockBuffer.text = 'hello world';
- mockBuffer.lines = ['hello world'];
- mockBuffer.cursor = [0, 5];
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
+ },
+ {
+ name: 'should NOT trigger completion when cursor is not after @ or /',
+ text: 'hello world',
+ cursor: [0, 5],
showSuggestions: false,
- suggestions: [],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should handle multiline text correctly', async () => {
- mockBuffer.text = 'first line\n/memory';
- mockBuffer.lines = ['first line', '/memory'];
- mockBuffer.cursor = [1, 7];
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
+ },
+ {
+ name: 'should handle multiline text correctly',
+ text: 'first line\n/memory',
+ cursor: [1, 7],
showSuggestions: false,
- suggestions: [],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- // Verify useCompletion was called with the buffer
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should handle single line slash command correctly', async () => {
- mockBuffer.text = '/memory';
- mockBuffer.lines = ['/memory'];
- mockBuffer.cursor = [0, 7];
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
+ },
+ {
+ name: 'should handle Unicode characters (emojis) correctly in paths',
+ text: '@src/fileπ.txt',
+ cursor: [0, 14],
showSuggestions: true,
- suggestions: [{ label: 'show', value: 'show' }],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should handle Unicode characters (emojis) correctly in paths', async () => {
- // Test with emoji in path after @
- mockBuffer.text = '@src/fileπ.txt';
- mockBuffer.lines = ['@src/fileπ.txt'];
- mockBuffer.cursor = [0, 14]; // After the emoji character
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
- showSuggestions: true,
- suggestions: [{ label: 'fileπ.txt', value: 'fileπ.txt' }],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should handle Unicode characters with spaces after them', async () => {
- // Test with emoji followed by space - should NOT trigger completion
- mockBuffer.text = '@src/fileπ.txt hello';
- mockBuffer.lines = ['@src/fileπ.txt hello'];
- mockBuffer.cursor = [0, 20]; // After the space
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
+ },
+ {
+ name: 'should handle Unicode characters with spaces after them',
+ text: '@src/fileπ.txt hello',
+ cursor: [0, 20],
showSuggestions: false,
- suggestions: [],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should handle escaped spaces in paths correctly', async () => {
- // Test with escaped space in path - should trigger completion
- mockBuffer.text = '@src/my\\ file.txt';
- mockBuffer.lines = ['@src/my\\ file.txt'];
- mockBuffer.cursor = [0, 16]; // After the escaped space and filename
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
+ },
+ {
+ name: 'should handle escaped spaces in paths correctly',
+ text: '@src/my\\ file.txt',
+ cursor: [0, 16],
showSuggestions: true,
- suggestions: [{ label: 'my file.txt', value: 'my file.txt' }],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should NOT trigger completion after unescaped space following escaped space', async () => {
- // Test: @path/my\ file.txt hello (unescaped space after escaped space)
- mockBuffer.text = '@path/my\\ file.txt hello';
- mockBuffer.lines = ['@path/my\\ file.txt hello'];
- mockBuffer.cursor = [0, 24]; // After "hello"
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
+ },
+ {
+ name: 'should NOT trigger completion after unescaped space following escaped space',
+ text: '@path/my\\ file.txt hello',
+ cursor: [0, 24],
showSuggestions: false,
- suggestions: [],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should handle multiple escaped spaces in paths', async () => {
- // Test with multiple escaped spaces
- mockBuffer.text = '@docs/my\\ long\\ file\\ name.md';
- mockBuffer.lines = ['@docs/my\\ long\\ file\\ name.md'];
- mockBuffer.cursor = [0, 29]; // At the end
+ },
+ {
+ name: 'should handle multiple escaped spaces in paths',
+ text: '@docs/my\\ long\\ file\\ name.md',
+ cursor: [0, 29],
+ showSuggestions: true,
+ },
+ {
+ name: 'should handle escaped spaces in slash commands',
+ text: '/memory\\ test',
+ cursor: [0, 13],
+ showSuggestions: true,
+ },
+ {
+ name: 'should handle Unicode characters with escaped spaces',
+ text: `@${path.join('files', 'emoji\\ π\\ test.txt')}`,
+ cursor: [0, 25],
+ showSuggestions: true,
+ },
+ ])('$name', async ({ text, cursor, showSuggestions }) => {
+ mockBuffer.text = text;
+ mockBuffer.lines = text.split('\n');
+ mockBuffer.cursor = cursor as [number, number];
mockedUseCommandCompletion.mockReturnValue({
...mockCommandCompletion,
- showSuggestions: true,
- suggestions: [
- { label: 'my long file name.md', value: 'my long file name.md' },
- ],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should handle escaped spaces in slash commands', async () => {
- // Test escaped spaces with slash commands (though less common)
- mockBuffer.text = '/memory\\ test';
- mockBuffer.lines = ['/memory\\ test'];
- mockBuffer.cursor = [0, 13]; // At the end
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
- showSuggestions: true,
- suggestions: [{ label: 'test-command', value: 'test-command' }],
- });
-
- const { unmount } = renderWithProviders();
- await wait();
-
- expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
- mockBuffer,
- ['/test/project/src'],
- path.join('test', 'project', 'src'),
- mockSlashCommands,
- mockCommandContext,
- false,
- false,
- expect.any(Object),
- );
-
- unmount();
- });
-
- it('should handle Unicode characters with escaped spaces', async () => {
- // Test combining Unicode and escaped spaces
- mockBuffer.text = '@' + path.join('files', 'emoji\\ π\\ test.txt');
- mockBuffer.lines = ['@' + path.join('files', 'emoji\\ π\\ test.txt')];
- mockBuffer.cursor = [0, 25]; // After the escaped space and emoji
-
- mockedUseCommandCompletion.mockReturnValue({
- ...mockCommandCompletion,
- showSuggestions: true,
- suggestions: [
- { label: 'emoji π test.txt', value: 'emoji π test.txt' },
- ],
+ showSuggestions,
+ suggestions: showSuggestions
+ ? [{ label: 'suggestion', value: 'suggestion' }]
+ : [],
});
const { unmount } = renderWithProviders();
@@ -1264,226 +967,156 @@ describe('InputPrompt', () => {
});
describe('Highlighting and Cursor Display', () => {
- it('should display cursor mid-word by highlighting the character', async () => {
- mockBuffer.text = 'hello world';
- mockBuffer.lines = ['hello world'];
- mockBuffer.viewportVisualLines = ['hello world'];
- mockBuffer.visualCursor = [0, 3]; // cursor on the second 'l'
+ describe('single-line scenarios', () => {
+ it.each([
+ {
+ name: 'mid-word',
+ text: 'hello world',
+ visualCursor: [0, 3],
+ expected: `hel${chalk.inverse('l')}o world`,
+ },
+ {
+ name: 'at the beginning of the line',
+ text: 'hello',
+ visualCursor: [0, 0],
+ expected: `${chalk.inverse('h')}ello`,
+ },
+ {
+ name: 'at the end of the line',
+ text: 'hello',
+ visualCursor: [0, 5],
+ expected: `hello${chalk.inverse(' ')}`,
+ },
+ {
+ name: 'on a highlighted token',
+ text: 'run @path/to/file',
+ visualCursor: [0, 9],
+ expected: `@path/${chalk.inverse('t')}o/file`,
+ },
+ {
+ name: 'for multi-byte unicode characters',
+ text: 'hello π world',
+ visualCursor: [0, 6],
+ expected: `hello ${chalk.inverse('π')} world`,
+ },
+ {
+ name: 'at the end of a line with unicode characters',
+ text: 'hello π',
+ visualCursor: [0, 8],
+ expected: `hello π${chalk.inverse(' ')}`,
+ },
+ {
+ name: 'on an empty line',
+ text: '',
+ visualCursor: [0, 0],
+ expected: chalk.inverse(' '),
+ },
+ {
+ name: 'on a space between words',
+ text: 'hello world',
+ visualCursor: [0, 5],
+ expected: `hello${chalk.inverse(' ')}world`,
+ },
+ ])(
+ 'should display cursor correctly $name',
+ async ({ text, visualCursor, expected }) => {
+ mockBuffer.text = text;
+ mockBuffer.lines = [text];
+ mockBuffer.viewportVisualLines = [text];
+ mockBuffer.visualCursor = visualCursor as [number, number];
- const { stdout, unmount } = renderWithProviders(
- ,
+ const { stdout, unmount } = renderWithProviders(
+ ,
+ );
+ await wait();
+
+ const frame = stdout.lastFrame();
+ expect(frame).toContain(expected);
+ unmount();
+ },
);
- await wait();
-
- const frame = stdout.lastFrame();
- // The component will render the text with the character at the cursor inverted.
- expect(frame).toContain(`hel${chalk.inverse('l')}o world`);
- unmount();
});
- it('should display cursor at the beginning of the line', async () => {
- mockBuffer.text = 'hello';
- mockBuffer.lines = ['hello'];
- mockBuffer.viewportVisualLines = ['hello'];
- mockBuffer.visualCursor = [0, 0]; // cursor on 'h'
+ describe('multi-line scenarios', () => {
+ it.each([
+ {
+ name: 'in the middle of a line',
+ text: 'first line\nsecond line\nthird line',
+ visualCursor: [1, 3],
+ visualToLogicalMap: [
+ [0, 0],
+ [1, 0],
+ [2, 0],
+ ],
+ expected: `sec${chalk.inverse('o')}nd line`,
+ },
+ {
+ name: 'at the beginning of a line',
+ text: 'first line\nsecond line',
+ visualCursor: [1, 0],
+ visualToLogicalMap: [
+ [0, 0],
+ [1, 0],
+ ],
+ expected: `${chalk.inverse('s')}econd line`,
+ },
+ {
+ name: 'at the end of a line',
+ text: 'first line\nsecond line',
+ visualCursor: [0, 10],
+ visualToLogicalMap: [
+ [0, 0],
+ [1, 0],
+ ],
+ expected: `first line${chalk.inverse(' ')}`,
+ },
+ ])(
+ 'should display cursor correctly $name in a multiline block',
+ async ({ text, visualCursor, expected, visualToLogicalMap }) => {
+ mockBuffer.text = text;
+ mockBuffer.lines = text.split('\n');
+ mockBuffer.viewportVisualLines = text.split('\n');
+ mockBuffer.visualCursor = visualCursor as [number, number];
+ mockBuffer.visualToLogicalMap = visualToLogicalMap as Array<
+ [number, number]
+ >;
- const { stdout, unmount } = renderWithProviders(
- ,
+ const { stdout, unmount } = renderWithProviders(
+ ,
+ );
+ await wait();
+
+ const frame = stdout.lastFrame();
+ expect(frame).toContain(expected);
+ unmount();
+ },
);
- await wait();
- const frame = stdout.lastFrame();
- expect(frame).toContain(`${chalk.inverse('h')}ello`);
- unmount();
- });
+ it('should display cursor on a blank line in a multiline block', async () => {
+ const text = 'first line\n\nthird line';
+ mockBuffer.text = text;
+ mockBuffer.lines = text.split('\n');
+ mockBuffer.viewportVisualLines = text.split('\n');
+ mockBuffer.visualCursor = [1, 0]; // cursor on the blank line
+ mockBuffer.visualToLogicalMap = [
+ [0, 0],
+ [1, 0],
+ [2, 0],
+ ];
- it('should display cursor at the end of the line as an inverted space', async () => {
- mockBuffer.text = 'hello';
- mockBuffer.lines = ['hello'];
- mockBuffer.viewportVisualLines = ['hello'];
- mockBuffer.visualCursor = [0, 5]; // cursor after 'o'
+ const { stdout, unmount } = renderWithProviders(
+ ,
+ );
+ await wait();
- const { stdout, unmount } = renderWithProviders(
- ,
- );
- await wait();
-
- const frame = stdout.lastFrame();
- expect(frame).toContain(`hello${chalk.inverse(' ')}`);
- unmount();
- });
-
- it('should display cursor correctly on a highlighted token', async () => {
- mockBuffer.text = 'run @path/to/file';
- mockBuffer.lines = ['run @path/to/file'];
- mockBuffer.viewportVisualLines = ['run @path/to/file'];
- mockBuffer.visualCursor = [0, 9]; // cursor on 't' in 'to'
-
- const { stdout, unmount } = renderWithProviders(
- ,
- );
- await wait();
-
- const frame = stdout.lastFrame();
- // The token '@path/to/file' is colored, and the cursor highlights one char inside it.
- expect(frame).toContain(`@path/${chalk.inverse('t')}o/file`);
- unmount();
- });
-
- it('should display cursor correctly for multi-byte unicode characters', async () => {
- const text = 'hello π world';
- mockBuffer.text = text;
- mockBuffer.lines = [text];
- mockBuffer.viewportVisualLines = [text];
- mockBuffer.visualCursor = [0, 6]; // cursor on 'π'
-
- const { stdout, unmount } = renderWithProviders(
- ,
- );
- await wait();
-
- const frame = stdout.lastFrame();
- expect(frame).toContain(`hello ${chalk.inverse('π')} world`);
- unmount();
- });
-
- it('should display cursor at the end of a line with unicode characters', async () => {
- const text = 'hello π';
- mockBuffer.text = text;
- mockBuffer.lines = [text];
- mockBuffer.viewportVisualLines = [text];
- mockBuffer.visualCursor = [0, 8]; // cursor after 'π' (length is 6 + 2 for emoji)
-
- const { stdout, unmount } = renderWithProviders(
- ,
- );
- await wait();
-
- const frame = stdout.lastFrame();
- expect(frame).toContain(`hello π${chalk.inverse(' ')}`);
- unmount();
- });
-
- it('should display cursor on an empty line', async () => {
- mockBuffer.text = '';
- mockBuffer.lines = [''];
- mockBuffer.viewportVisualLines = [''];
- mockBuffer.visualCursor = [0, 0];
-
- const { stdout, unmount } = renderWithProviders(
- ,
- );
- await wait();
-
- const frame = stdout.lastFrame();
- expect(frame).toContain(chalk.inverse(' '));
- unmount();
- });
-
- it('should display cursor on a space between words', async () => {
- mockBuffer.text = 'hello world';
- mockBuffer.lines = ['hello world'];
- mockBuffer.viewportVisualLines = ['hello world'];
- mockBuffer.visualCursor = [0, 5]; // cursor on the space
-
- const { stdout, unmount } = renderWithProviders(
- ,
- );
- await wait();
-
- const frame = stdout.lastFrame();
- expect(frame).toContain(`hello${chalk.inverse(' ')}world`);
- unmount();
- });
-
- it('should display cursor in the middle of a line in a multiline block', async () => {
- const text = 'first line\nsecond line\nthird line';
- mockBuffer.text = text;
- mockBuffer.lines = text.split('\n');
- mockBuffer.viewportVisualLines = text.split('\n');
- mockBuffer.visualCursor = [1, 3]; // cursor on 'o' in 'second'
- mockBuffer.visualToLogicalMap = [
- [0, 0],
- [1, 0],
- [2, 0],
- ];
-
- const { stdout, unmount } = renderWithProviders(
- ,
- );
- await wait();
-
- const frame = stdout.lastFrame();
- expect(frame).toContain(`sec${chalk.inverse('o')}nd line`);
- unmount();
- });
-
- it('should display cursor at the beginning of a line in a multiline block', async () => {
- const text = 'first line\nsecond line';
- mockBuffer.text = text;
- mockBuffer.lines = text.split('\n');
- mockBuffer.viewportVisualLines = text.split('\n');
- mockBuffer.visualCursor = [1, 0]; // cursor on 's' in 'second'
- mockBuffer.visualToLogicalMap = [
- [0, 0],
- [1, 0],
- ];
-
- const { stdout, unmount } = renderWithProviders(
- ,
- );
- await wait();
-
- const frame = stdout.lastFrame();
- expect(frame).toContain(`${chalk.inverse('s')}econd line`);
- unmount();
- });
-
- it('should display cursor at the end of a line in a multiline block', async () => {
- const text = 'first line\nsecond line';
- mockBuffer.text = text;
- mockBuffer.lines = text.split('\n');
- mockBuffer.viewportVisualLines = text.split('\n');
- mockBuffer.visualCursor = [0, 10]; // cursor after 'first line'
- mockBuffer.visualToLogicalMap = [
- [0, 0],
- [1, 0],
- ];
-
- const { stdout, unmount } = renderWithProviders(
- ,
- );
- await wait();
-
- const frame = stdout.lastFrame();
- expect(frame).toContain(`first line${chalk.inverse(' ')}`);
- unmount();
- });
-
- it('should display cursor on a blank line in a multiline block', async () => {
- const text = 'first line\n\nthird line';
- mockBuffer.text = text;
- mockBuffer.lines = text.split('\n');
- mockBuffer.viewportVisualLines = text.split('\n');
- mockBuffer.visualCursor = [1, 0]; // cursor on the blank line
- mockBuffer.visualToLogicalMap = [
- [0, 0],
- [1, 0],
- [2, 0],
- ];
-
- const { stdout, unmount } = renderWithProviders(
- ,
- );
- await wait();
-
- const frame = stdout.lastFrame();
- const lines = frame!.split('\n');
- // The line with the cursor should just be an inverted space inside the box border
- expect(
- lines.find((l) => l.includes(chalk.inverse(' '))),
- ).not.toBeUndefined();
- unmount();
+ const frame = stdout.lastFrame();
+ const lines = frame!.split('\n');
+ // The line with the cursor should just be an inverted space inside the box border
+ expect(
+ lines.find((l) => l.includes(chalk.inverse(' '))),
+ ).not.toBeUndefined();
+ unmount();
+ });
});
});
@@ -2403,55 +2036,58 @@ describe('InputPrompt', () => {
expect(mockBuffer.handleInput).toHaveBeenCalled();
unmount();
});
- it('should prevent slash commands from being queued while streaming', async () => {
- props.onSubmit = vi.fn();
- props.buffer.text = '/help';
- props.setQueueErrorMessage = vi.fn();
- props.streamingState = StreamingState.Responding;
- const { stdin, unmount } = renderWithProviders();
- await wait();
- stdin.write('/help');
- stdin.write('\r');
- await wait();
+ describe('command queuing while streaming', () => {
+ beforeEach(() => {
+ props.streamingState = StreamingState.Responding;
+ props.setQueueErrorMessage = vi.fn();
+ props.onSubmit = vi.fn();
+ });
- expect(props.onSubmit).not.toHaveBeenCalled();
- expect(props.setQueueErrorMessage).toHaveBeenCalledWith(
- 'Slash commands cannot be queued',
+ it.each([
+ {
+ name: 'should prevent slash commands',
+ bufferText: '/help',
+ shellMode: false,
+ shouldSubmit: false,
+ errorMessage: 'Slash commands cannot be queued',
+ },
+ {
+ name: 'should prevent shell commands',
+ bufferText: 'ls',
+ shellMode: true,
+ shouldSubmit: false,
+ errorMessage: 'Shell commands cannot be queued',
+ },
+ {
+ name: 'should allow regular messages',
+ bufferText: 'regular message',
+ shellMode: false,
+ shouldSubmit: true,
+ errorMessage: null,
+ },
+ ])(
+ '$name',
+ async ({ bufferText, shellMode, shouldSubmit, errorMessage }) => {
+ props.buffer.text = bufferText;
+ props.shellModeActive = shellMode;
+
+ const { stdin, unmount } = renderWithProviders(
+ ,
+ );
+ await wait();
+ stdin.write('\r');
+ await wait();
+
+ if (shouldSubmit) {
+ expect(props.onSubmit).toHaveBeenCalledWith(bufferText);
+ expect(props.setQueueErrorMessage).not.toHaveBeenCalled();
+ } else {
+ expect(props.onSubmit).not.toHaveBeenCalled();
+ expect(props.setQueueErrorMessage).toHaveBeenCalledWith(errorMessage);
+ }
+ unmount();
+ },
);
- unmount();
- });
- it('should prevent shell commands from being queued while streaming', async () => {
- props.onSubmit = vi.fn();
- props.buffer.text = 'ls';
- props.setQueueErrorMessage = vi.fn();
- props.streamingState = StreamingState.Responding;
- props.shellModeActive = true;
- const { stdin, unmount } = renderWithProviders();
- await wait();
- stdin.write('ls');
- stdin.write('\r');
- await wait();
-
- expect(props.onSubmit).not.toHaveBeenCalled();
- expect(props.setQueueErrorMessage).toHaveBeenCalledWith(
- 'Shell commands cannot be queued',
- );
- unmount();
- });
- it('should allow regular messages to be queued while streaming', async () => {
- props.onSubmit = vi.fn();
- props.buffer.text = 'regular message';
- props.setQueueErrorMessage = vi.fn();
- props.streamingState = StreamingState.Responding;
- const { stdin, unmount } = renderWithProviders();
- await wait();
- stdin.write('regular message');
- stdin.write('\r');
- await wait();
-
- expect(props.onSubmit).toHaveBeenCalledWith('regular message');
- expect(props.setQueueErrorMessage).not.toHaveBeenCalled();
- unmount();
});
});