mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 00:03:08 +00:00
* feat: 모바일 터미널 한글 미러 스텝 순수 모델 추가 * feat: 미러 델타 순서 보장용 send 체인 추가 * fix: 모바일 터미널 한글 입력을 미러 모델로 전환 * fix: 탭 상태 지연 중 한글 조합 상태 소실 방지 * fix: 미러 가드와 send 체인 리뷰 지적사항 반영 탭 상태 지연으로 활성 탭 타입이 일시적으로 null이 될 때 runMirrorStep의 stale-handle 가드가 조합 중 음절을 버리지 않도록 pending-clear 효과와 동일한 null 허용 패턴 적용. 테스트 하네스가 ref와 prop을 동일 소스에서 파생하도록 결합해 실제 경로의 lag 프레임을 검증. queueTerminalLiveMirrorSend의 previousSend await를 catch로 보호. * refactor(mobile): drop dead queueTerminalLivePendingFlush orphaned by the mirror model The mirror model migrated all live-input sends to queueTerminalLiveMirrorSend, leaving queueTerminalLivePendingFlush referenced only by its own tests. Remove the dead function and its three tests. Co-authored-by: Orca <help@stably.ai> * fix(mobile): expose live terminal keyboard target Co-authored-by: Orca <help@stably.ai> * fix(mobile): refocus live keyboard after dismissal Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: realitsyourman <wongil@demodev.io> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai>
119 lines
3.8 KiB
TypeScript
119 lines
3.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
getTerminalLiveAccessoryBytesDecision,
|
|
getTerminalLiveAccessoryLocalEditText,
|
|
getTerminalLiveSpecialKeyDecision
|
|
} from './terminal-live-text-commit'
|
|
|
|
describe('terminal live special key decision', () => {
|
|
it('Given an unmapped key Then ignores it', () => {
|
|
expect(getTerminalLiveSpecialKeyDecision({ key: 'ㅎ', heldText: '', sentText: '' })).toEqual({
|
|
kind: 'ignore'
|
|
})
|
|
})
|
|
|
|
it('Given Backspace with any field text Then edits locally so the mirror diff handles the PTY erase', () => {
|
|
// Held syllable present
|
|
expect(
|
|
getTerminalLiveSpecialKeyDecision({ key: 'Backspace', heldText: '한', sentText: '' })
|
|
).toEqual({ kind: 'local-edit' })
|
|
// Only mirrored text present — native edit fires onChangeText and the diff erases
|
|
expect(
|
|
getTerminalLiveSpecialKeyDecision({ key: 'Backspace', heldText: '', sentText: 'abc' })
|
|
).toEqual({ kind: 'local-edit' })
|
|
})
|
|
|
|
it('Given Backspace with an empty field Then sends terminal backspace bytes', () => {
|
|
const decision = getTerminalLiveSpecialKeyDecision({
|
|
key: 'Backspace',
|
|
heldText: '',
|
|
sentText: ''
|
|
})
|
|
expect(decision.kind).toBe('send-now')
|
|
})
|
|
|
|
it('Given a control key with a held syllable Then commits the held text before the bytes', () => {
|
|
const decision = getTerminalLiveSpecialKeyDecision({
|
|
key: 'Tab',
|
|
heldText: '글',
|
|
sentText: '한'
|
|
})
|
|
expect(decision.kind).toBe('commit-held-then-send')
|
|
})
|
|
|
|
it('Given a control key with no held syllable Then sends immediately', () => {
|
|
const decision = getTerminalLiveSpecialKeyDecision({
|
|
key: 'ArrowUp',
|
|
heldText: '',
|
|
sentText: 'ls'
|
|
})
|
|
expect(decision.kind).toBe('send-now')
|
|
})
|
|
})
|
|
|
|
describe('terminal live accessory bytes decision', () => {
|
|
it('Given a local-edit accessory key with field text Then edits locally', () => {
|
|
expect(
|
|
getTerminalLiveAccessoryBytesDecision({
|
|
bytes: '\x7f',
|
|
localEdit: 'backspace',
|
|
heldText: '한',
|
|
sentText: ''
|
|
})
|
|
).toEqual({ kind: 'local-edit', localEdit: 'backspace' })
|
|
expect(
|
|
getTerminalLiveAccessoryBytesDecision({
|
|
bytes: '\x7f',
|
|
localEdit: 'backspace',
|
|
heldText: '',
|
|
sentText: 'abc'
|
|
})
|
|
).toEqual({ kind: 'local-edit', localEdit: 'backspace' })
|
|
})
|
|
|
|
it('Given raw accessory bytes with a held syllable Then commits held text first', () => {
|
|
const decision = getTerminalLiveAccessoryBytesDecision({
|
|
bytes: '\x1b',
|
|
heldText: '한',
|
|
sentText: ''
|
|
})
|
|
expect(decision).toEqual({ kind: 'commit-held-then-send', bytes: '\x1b' })
|
|
})
|
|
|
|
it('Given raw accessory bytes with nothing held Then sends immediately', () => {
|
|
const decision = getTerminalLiveAccessoryBytesDecision({
|
|
bytes: '\x1b',
|
|
heldText: '',
|
|
sentText: 'abc'
|
|
})
|
|
expect(decision).toEqual({ kind: 'send-now', bytes: '\x1b' })
|
|
})
|
|
|
|
it('Given a local-edit accessory key with an empty field Then sends the raw bytes', () => {
|
|
const decision = getTerminalLiveAccessoryBytesDecision({
|
|
bytes: '\x7f',
|
|
localEdit: 'backspace',
|
|
heldText: '',
|
|
sentText: ''
|
|
})
|
|
expect(decision).toEqual({ kind: 'send-now', bytes: '\x7f' })
|
|
})
|
|
})
|
|
|
|
describe('terminal live accessory local edit text', () => {
|
|
it('Given backspace Then drops the last code point of the field text', () => {
|
|
expect(
|
|
getTerminalLiveAccessoryLocalEditText({ localEdit: 'backspace', fieldText: '한글' })
|
|
).toBe('한')
|
|
expect(getTerminalLiveAccessoryLocalEditText({ localEdit: 'backspace', fieldText: '한' })).toBe(
|
|
''
|
|
)
|
|
})
|
|
|
|
it('Given forward delete Then keeps the field text unchanged', () => {
|
|
expect(getTerminalLiveAccessoryLocalEditText({ localEdit: 'delete', fieldText: '한글' })).toBe(
|
|
'한글'
|
|
)
|
|
})
|
|
})
|