Files
archy/neode-ui/src/api/__tests__/remote-relay.test.ts
T

111 lines
3.7 KiB
TypeScript
Raw Normal View History

2026-08-12 10:55:49 +00:00
import { describe, it, expect, beforeEach } from 'vitest'
import { isTextField, typeKeyIntoField } from '../remote-relay'
/**
* Companion-cursor text entry. Synthetic KeyboardEvents do NOT mutate input
* values in the browser, so the relay edits `.value` at the caret directly and
* fires an `input` event. These tests lock in that behaviour so a regression
* (like the old "type goes to document, nothing happens" bug) is caught before
* release rather than by a user with a companion controller.
*/
describe('isTextField', () => {
it('accepts text-like inputs and textareas', () => {
const text = document.createElement('input')
text.type = 'text'
const search = document.createElement('input')
search.type = 'search'
const area = document.createElement('textarea')
expect(isTextField(text)).toBe(true)
expect(isTextField(search)).toBe(true)
expect(isTextField(area)).toBe(true)
})
it('rejects non-text controls and null', () => {
const checkbox = document.createElement('input')
checkbox.type = 'checkbox'
expect(isTextField(checkbox)).toBe(false)
expect(isTextField(document.createElement('button'))).toBe(false)
expect(isTextField(document.createElement('div'))).toBe(false)
expect(isTextField(null)).toBe(false)
})
})
describe('typeKeyIntoField', () => {
let input: HTMLInputElement
let inputEvents: number
beforeEach(() => {
input = document.createElement('input')
input.type = 'search'
document.body.appendChild(input)
inputEvents = 0
input.addEventListener('input', () => { inputEvents++ })
})
it('inserts printable characters at the caret and fires input', () => {
typeKeyIntoField(input, 'b')
typeKeyIntoField(input, 't')
typeKeyIntoField(input, 'c')
expect(input.value).toBe('btc')
expect(input.selectionStart).toBe(3)
expect(inputEvents).toBe(3)
})
it('inserts a character in the middle of existing text', () => {
input.value = 'bc'
input.selectionStart = input.selectionEnd = 1
typeKeyIntoField(input, 't')
expect(input.value).toBe('btc')
expect(input.selectionStart).toBe(2)
})
it('backspace deletes the char before the caret', () => {
input.value = 'btc'
input.selectionStart = input.selectionEnd = 3
typeKeyIntoField(input, 'Backspace')
expect(input.value).toBe('bt')
expect(input.selectionStart).toBe(2)
})
it('backspace removes the active selection', () => {
input.value = 'bitcoin'
input.selectionStart = 0
input.selectionEnd = 3
typeKeyIntoField(input, 'Backspace')
expect(input.value).toBe('coin')
expect(input.selectionStart).toBe(0)
})
it('arrow keys move the caret without changing the value', () => {
input.value = 'abc'
input.selectionStart = input.selectionEnd = 3
typeKeyIntoField(input, 'ArrowLeft')
expect(input.selectionStart).toBe(2)
expect(input.value).toBe('abc')
})
it('Enter on a single-line input is left for the app to handle', () => {
input.value = 'query'
input.selectionStart = input.selectionEnd = 5
const consumed = typeKeyIntoField(input, 'Enter')
expect(consumed).toBe(false)
expect(input.value).toBe('query')
})
it('Enter inserts a newline in a textarea', () => {
const area = document.createElement('textarea')
area.value = 'a'
area.selectionStart = area.selectionEnd = 1
expect(typeKeyIntoField(area, 'Enter')).toBe(true)
expect(area.value).toBe('a\n')
})
it('non-text keys are not consumed as editing', () => {
input.value = 'x'
input.selectionStart = input.selectionEnd = 1
expect(typeKeyIntoField(input, 'Escape')).toBe(false)
expect(typeKeyIntoField(input, 'Tab')).toBe(false)
expect(input.value).toBe('x')
})
})