diff --git a/cli/src/components/__tests__/multiline-input.test.tsx b/cli/src/components/__tests__/multiline-input.test.tsx index 7fcf7eaa17..34eabb88ac 100644 --- a/cli/src/components/__tests__/multiline-input.test.tsx +++ b/cli/src/components/__tests__/multiline-input.test.tsx @@ -1,9 +1,17 @@ -import { describe, test, expect } from 'bun:test' +import { afterEach, beforeAll, describe, test, expect } from 'bun:test' +import { createTestRenderer } from '@opentui/core/testing' +import { createRoot, flushSync } from '@opentui/react' +import React, { useState } from 'react' import { getKeypadPrintableSequence, isKeypadEnter, } from '../../utils/keypad-keys' +import { initializeThemeStore } from '../../hooks/use-theme' +import { useChatStore } from '../../state/chat-store' +import { MultilineInput } from '../multiline-input' + +import type { InputValue } from '../../types/store' /** * Tests for tab character cursor rendering in MultilineInput component. @@ -1130,3 +1138,63 @@ describe('MultilineInput - newline keyboard shortcuts', () => { expect(isAltModifier({ option: false })).toBe(false) }) }) + +/** + * Ctrl+Backspace is the word-delete key in a terminal, but the handler used to + * only accept Alt+Backspace or Ctrl+W. This mounts the component so the check + * runs against real key events rather than a copy of the condition. + */ +describe('MultilineInput - Ctrl+Backspace', () => { + beforeAll(() => { + initializeThemeStore() + }) + + afterEach(() => { + useChatStore.getState().reset() + }) + + test('deletes the word before the cursor', async () => { + let change: InputValue | undefined + + const Harness = () => { + const [value, setValue] = useState('hello world') + return ( + { + change = next + setValue(next.text) + }} + onSubmit={() => {}} + onPaste={() => {}} + focused + cursorPosition={value.length} + maxHeight={4} + /> + ) + } + + const setup = await createTestRenderer({ + width: 60, + height: 10, + kittyKeyboard: true, + }) + const root = createRoot(setup.renderer) + try { + flushSync(() => root.render()) + await setup.renderOnce() + + flushSync(() => setup.mockInput.pressKey('BACKSPACE', { ctrl: true })) + await setup.renderOnce() + + expect(change).toEqual({ + text: 'hello ', + cursorPosition: 6, + lastEditDueToNav: false, + }) + } finally { + flushSync(() => root.unmount()) + setup.renderer.destroy() + } + }) +}) diff --git a/cli/src/components/multiline-input.tsx b/cli/src/components/multiline-input.tsx index c5db1c51fd..80d3b41a40 100644 --- a/cli/src/components/multiline-input.tsx +++ b/cli/src/components/multiline-input.tsx @@ -683,9 +683,9 @@ export const MultilineInput = forwardRef< return true } - // Alt+Backspace or Ctrl+W: Delete word backward + // Alt/Ctrl+Backspace or Ctrl+W: Delete word backward if ( - (key.name === 'backspace' && isAltLikeModifier) || + (key.name === 'backspace' && (isAltLikeModifier || key.ctrl)) || (key.ctrl && lowerKeyName === 'w') ) { preventKeyDefault(key)