Files
Aislo/B07_DesignDetail/openwebcad/src/inputController/ime-sink.test.ts
T

141 lines
4.7 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { Actor } from 'xstate';
import { MouseButton } from '../App.types.ts';
import { getActiveToolActor, setActiveToolActor } from '../state.ts';
import { Tool } from '../tools.ts';
import { createSequenceTool } from '../tools/factories/sequence-tool.ts';
import { selectToolStateMachine } from '../tools/select-tool.ts';
import { activeToolWantsFreeText, ImeSink, isImeKey } from './ime-sink.ts';
import { InputController } from './input-controller.ts';
describe('한글 IME 입력', () => {
it('조합 사건으로 「가나다」 가 들어간다', () => {
let text = '';
const sink = new ImeSink((data) => {
text += data;
});
for (const [steps, done] of [
[['ㄱ', '가'], '가'],
[['ㄴ', '나'], '나'],
[['ㄷ', '다'], '다'],
] as const) {
for (const step of steps) sink.handleCompositionUpdate(step);
expect(sink.composing).toBe(steps[steps.length - 1]);
sink.handleCompositionEnd(done);
}
expect(text).toBe('가나다');
expect(sink.composing).toBe('');
// 조합 중 input 은 compositionend 몫 — 겹쳐 넣지 않음 · 조합 뒤 띄어쓰기는 받음
sink.handleInput({ isComposing: true, data: '다' });
sink.handleInput({ isComposing: false, data: ' ' });
expect(text).toBe('가나다 ');
});
it('조합 중 키는 단축키로 먹지 않는다', () => {
expect(isImeKey({ isComposing: true, keyCode: 0, key: 'r' } as KeyboardEvent)).toBe(true);
expect(isImeKey({ isComposing: false, keyCode: 229, key: 'r' } as KeyboardEvent)).toBe(true);
expect(isImeKey({ isComposing: false, keyCode: 0, key: 'Process' } as KeyboardEvent)).toBe(
true
);
expect(isImeKey({ isComposing: false, keyCode: 76, key: 'l' } as KeyboardEvent)).toBe(false);
const controller = new InputController();
const preventDefault = vi.fn();
controller.handleKeyStroke({
key: 'Process',
keyCode: 229,
isComposing: true,
target: null,
preventDefault,
stopPropagation: vi.fn(),
} as unknown as KeyboardEvent);
expect(preventDefault).not.toHaveBeenCalled();
controller.ime.handleCompositionEnd('가나다');
expect((controller as unknown as { text: string }).text).toBe('가나다');
});
});
describe('Space = Enter(오토캐드)', () => {
const press = (controller: InputController, key: string, extra: Partial<KeyboardEvent> = {}) =>
controller.handleKeyStroke({
key,
keyCode: 0,
isComposing: false,
shiftKey: false,
ctrlKey: false,
altKey: false,
metaKey: false,
repeat: false,
target: null,
preventDefault: vi.fn(),
stopPropagation: vi.fn(),
...extra,
} as unknown as KeyboardEvent);
const textOf = (controller: InputController) => (controller as unknown as { text: string }).text;
const activeTool = () => getActiveToolActor()?.getSnapshot()?.context?.type;
const selectIdle = () => setActiveToolActor(new Actor(selectToolStateMachine), false);
it('「L␣」 = LINE 실행', () => {
selectIdle();
const controller = new InputController();
press(controller, 'l');
press(controller, ' ');
expect(activeTool()).toBe(Tool.LINE);
expect(textOf(controller)).toBe('');
});
it('조합 중 Space · Shift/Ctrl+Space · 길게 눌러 반복은 무시', () => {
selectIdle();
const controller = new InputController();
press(controller, 'l');
press(controller, ' ', { isComposing: true, keyCode: 229 });
press(controller, ' ', { shiftKey: true });
press(controller, ' ', { ctrlKey: true });
press(controller, ' ', { repeat: true });
expect(textOf(controller)).toBe('L');
expect(activeTool()).toBe(Tool.SELECT);
});
it('글 단계(TEXT 등)에서는 띄어쓰기', () => {
const machine = createSequenceTool({
tool: Tool.TEXT,
steps: [{ kind: 'text', instructions: '문자를 입력하십시오.' }],
commit: () => {},
});
setActiveToolActor(new Actor(machine), false);
expect(activeToolWantsFreeText()).toBe(true);
const controller = new InputController();
press(controller, 'a');
press(controller, ' ');
press(controller, 'b');
expect(textOf(controller)).toBe('a b');
expect(activeTool()).toBe(Tool.TEXT);
});
it('빈 Space · Enter = 마지막 명령 반복 · 빈 우클릭은 반복 안 함', () => {
selectIdle();
const controller = new InputController();
press(controller, 'l');
press(controller, 'Enter');
expect(activeTool()).toBe(Tool.LINE);
selectIdle();
expect(activeToolWantsFreeText()).toBe(false);
press(controller, ' ');
expect(activeTool()).toBe(Tool.LINE);
selectIdle();
press(controller, 'Enter');
expect(activeTool()).toBe(Tool.LINE);
selectIdle();
controller.handleMouseUp({
button: MouseButton.Right,
target: null,
preventDefault: vi.fn(),
stopPropagation: vi.fn(),
} as unknown as MouseEvent);
expect(activeTool()).toBe(Tool.SELECT);
});
});