diff --git a/source/components/user-input.spec.tsx b/source/components/user-input.spec.tsx index 2e57a44f..eafd4a2b 100644 --- a/source/components/user-input.spec.tsx +++ b/source/components/user-input.spec.tsx @@ -3,7 +3,7 @@ import {render} from 'ink-testing-library'; import React from 'react'; import {themes} from '../config/themes'; import {ThemeContext} from '../hooks/useTheme'; -import {UIStateProvider} from '../hooks/useUIState'; +import {UIStateProvider, useUIStateContext} from '../hooks/useUIState'; import UserInput from './user-input'; console.log(`\nuser-input.spec.tsx – ${React.version}`); @@ -393,6 +393,7 @@ test('UserInput does not show ctrl-o hint when onToggleCompactDisplay is not pro unmount(); }); + // ============================================================================ // Command Completion Navigation Tests // ============================================================================ @@ -484,3 +485,91 @@ test('completion menu dismissal/reset after selection or escape', async t => { unmount(); }); + +test('UserInput renders completions text when typing /', async t => { + const {stdin, lastFrame, unmount} = render( + + + + ); + + await new Promise(resolve => setTimeout(resolve, 50)); + stdin.write('/'); + await new Promise(resolve => setTimeout(resolve, 150)); + + const output = lastFrame()!; + t.truthy(output); + t.regex(output, /Available commands:/); + unmount(); +}); + +test('UserInput renders completions BEFORE the mode indicator (inside the input box)', async t => { + const {stdin, lastFrame, unmount} = render( + + + + ); + + await new Promise(resolve => setTimeout(resolve, 50)); + stdin.write('/'); + await new Promise(resolve => setTimeout(resolve, 150)); + + const output = lastFrame()!; + t.truthy(output); + + const completionsIdx = output.indexOf('Available commands:'); + const modeIdx = output.indexOf('normal mode'); + t.true(completionsIdx > -1, 'Completions text should be present'); + t.true(modeIdx > -1, 'Mode indicator should be present'); + t.true( + completionsIdx < modeIdx, + 'Completions must render before the mode indicator (inside the bordered input box)', + ); + unmount(); +}); + +test('UserInput completions appear on a line above the mode indicator', async t => { + const {stdin, lastFrame, unmount} = render( + + + + ); + + await new Promise(resolve => setTimeout(resolve, 50)); + stdin.write('/'); + await new Promise(resolve => setTimeout(resolve, 150)); + + const output = lastFrame()!; + const lines = output.split('\n'); + + let completionLine = -1; + let modeLine = -1; + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes('Available commands:')) completionLine = i; + if (lines[i].includes('normal mode')) modeLine = i; + } + + t.true(completionLine > -1, 'Should find completions line'); + t.true(modeLine > -1, 'Should find mode indicator line'); + t.true( + completionLine < modeLine, + `Completions (line ${completionLine}) must be above mode indicator (line ${modeLine})`, + ); + unmount(); +}); + +test('UserInput does not show completions when input is empty', t => { + const {lastFrame, unmount} = render( + + + + ); + + const output = lastFrame()!; + t.truthy(output); + t.notRegex(output, /Available commands:/); + unmount(); +}); + + + diff --git a/source/components/user-input.tsx b/source/components/user-input.tsx index a291bbb1..b654f8b3 100644 --- a/source/components/user-input.tsx +++ b/source/components/user-input.tsx @@ -651,47 +651,6 @@ export default function UserInput({ )} - {showCompletions && completions.length > 0 && ( - - Available commands: - {completions.map((completion, index) => { - const isSelected = index === selectedCompletionIndex; - return ( - - {isSelected ? '▸ ' : ' '}/{completion.name} - - ); - })} - - )} - {isFileAutocompleteMode && fileCompletions.length > 0 && ( - - - File suggestions (↑/↓ to navigate, Tab to select): - - {fileCompletions.slice(0, 5).map((file, index) => ( - - {index === selectedFileIndex ? '▸ ' : ' '} - {file.path} - - ))} - - )} - Press escape again to clear )} + + {showCompletions && completions.length > 0 && ( + + Available commands: + {completions.map((completion, index) => { + const isSelected = index === selectedCompletionIndex; + return ( + + {isSelected ? '▸ ' : ' '}/{completion.name} + + ); + })} + + )} + {isFileAutocompleteMode && fileCompletions.length > 0 && ( + + + File suggestions (↑/↓ to navigate, Tab to select): + + {fileCompletions.slice(0, 5).map((file, index) => ( + + {index === selectedFileIndex ? '▸ ' : ' '} + {file.path} + + ))} + + )} {attachments.length > 0 && ( @@ -738,7 +740,6 @@ export default function UserInput({ · ctrl-x remove last )} - {/* Development mode indicator - always visible */}