24af6685b0
The dashboard maps Ctrl+Delete to ESC d for delete-word-forward, but the composer had no binding for it: hermes-ink decodes ESC d as meta+'d', which fell through to the printable path and typed a literal "d" instead of deleting the next word. Add a meta+d branch that mirrors the existing Ctrl+W delete-word-backward, sharing a deleteWordForward helper with the Delete+word path. The new cut() wrote the clipboard fire-and-forget and removed the selected text immediately, so on a headless/SSH box with no clipboard backend the write fails and the text is lost with no copy to paste back. Make cut transactional via cutSelection(): await the write and only remove the selection when it succeeds; on failure the selection stays intact. The removal also re-checks the selection to avoid slicing with stale offsets after the awaited write.
54 lines
1.6 KiB
TypeScript
54 lines
1.6 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
import { cutSelection } from '../components/textInput.js'
|
|
|
|
describe('cutSelection (transactional cut)', () => {
|
|
it('removes the selection only after the clipboard write succeeds', async () => {
|
|
const write = vi.fn().mockResolvedValue(true)
|
|
const removeSelection = vi.fn()
|
|
|
|
const ok = await cutSelection('hello', write, removeSelection)
|
|
|
|
expect(ok).toBe(true)
|
|
expect(write).toHaveBeenCalledWith('hello')
|
|
expect(removeSelection).toHaveBeenCalledOnce()
|
|
})
|
|
|
|
it('keeps the text intact when the clipboard write fails (headless/SSH)', async () => {
|
|
const write = vi.fn().mockResolvedValue(false)
|
|
const removeSelection = vi.fn()
|
|
|
|
const ok = await cutSelection('hello', write, removeSelection)
|
|
|
|
expect(ok).toBe(false)
|
|
expect(write).toHaveBeenCalledWith('hello')
|
|
// Text must NOT be removed. A failed write would otherwise destroy it with no
|
|
// clipboard copy to paste back.
|
|
expect(removeSelection).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('awaits the write before removing (no fire-and-forget removal)', async () => {
|
|
let resolveWrite: (value: boolean) => void = () => {}
|
|
|
|
const write = vi.fn(
|
|
() =>
|
|
new Promise<boolean>(resolve => {
|
|
resolveWrite = resolve
|
|
})
|
|
)
|
|
|
|
const removeSelection = vi.fn()
|
|
|
|
const pending = cutSelection('hello', write, removeSelection)
|
|
|
|
// While the write is still pending the selection must remain untouched.
|
|
await Promise.resolve()
|
|
expect(removeSelection).not.toHaveBeenCalled()
|
|
|
|
resolveWrite(true)
|
|
await pending
|
|
|
|
expect(removeSelection).toHaveBeenCalledOnce()
|
|
})
|
|
})
|