Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion cli/src/components/__tests__/multiline-input.test.tsx
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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 (
<MultilineInput
value={value}
onChange={(next) => {
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(<Harness />))
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()
}
})
})
4 changes: 2 additions & 2 deletions cli/src/components/multiline-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading