/** * CAD 시험 공용 조작(337 설계 6-2) — 빈 도면 · 쉬는 상태(선택 도구)에서 키 · 마우스를 사람처럼 넣음. * 시험 쪽 flatten-js 는 다른 사본(「Illegal Parameters」) — 점은 앱 코드로만 만들고 숫자로 판정. * 시험 캔버스는 축척 1 · y 위 + · 세계 (x, y) = 화면 (x, 높이 − y). */ import { vi } from "vitest"; import type { InputController } from "../../../../B07_DesignDetail/openwebcad/src/inputController/input-controller"; import { currentNotice, currentPrompt, } from "../../../../B07_DesignDetail/openwebcad/src/inputController/prompt-input"; import { getActiveToolActor, getEntities, setSelectedEntityIds, } from "../../../../B07_DesignDetail/openwebcad/src/state"; import { ActorEvent } from "../../../../B07_DesignDetail/openwebcad/src/tools/tool.types"; import { initApplication } from "../../../../B07_DesignDetail/openwebcad/test/helpers/init-application"; import { CANVAS_HEIGHT } from "../../../../B07_DesignDetail/openwebcad/test/helpers/tests.consts"; export type Mods = Partial> & { code?: string; repeat?: boolean; }; let controller: InputController; /** 빈 도면 · 고른 것 없음 · 선택 도구(쉼)로 시작 — 앱 도우미가 켠 LINE 첫 물음에서 Esc */ export function initIdle(): InputController { controller = initApplication(); getActiveToolActor()?.send({ type: ActorEvent.ESC }); setSelectedEntityIds([]); return controller; } export const driver = (): InputController => controller; /** 키 하나 — `key` 는 KeyboardEvent.key 꼴(「a」 · 「Enter」 · 「Escape」 · 「 」) */ export function key(k: string, mods: Mods = {}) { const preventDefault = vi.fn(); controller.handleKeyStroke({ key: k, code: mods.code ?? "", keyCode: 0, isComposing: false, shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, repeat: false, target: null, preventDefault, stopPropagation: vi.fn(), ...mods, } as unknown as KeyboardEvent); return preventDefault; } /** 글자만 침(제출 안 함) */ export const typed = (text: string) => { for (const ch of text) key(ch); }; /** 글자 + Space(= Enter) */ export const type = (text: string) => { typed(text); key(" "); }; /** 글자 + Enter */ export const typeEnter = (text: string) => { typed(text); key("Enter"); }; const mouse = (x: number, y: number, button: number, mods: Mods = {}) => ({ button, clientX: x, clientY: CANVAS_HEIGHT - y, target: null, shiftKey: false, ctrlKey: false, preventDefault: () => {}, stopPropagation: () => {}, ...mods, }) as unknown as MouseEvent; /** 세계 좌표 (x, y) 로 커서 이동 */ export const move = (x: number, y: number) => controller.handleMouseMove({ clientX: x, clientY: CANVAS_HEIGHT - y } as MouseEvent); export const press = (x: number, y: number, mods: Mods = {}) => { move(x, y); controller.handleMouseDown(mouse(x, y, 0, mods)); }; export const release = (x: number, y: number, mods: Mods = {}) => { move(x, y); controller.handleMouseUp(mouse(x, y, 0, mods)); }; /** 왼쪽 누름 한 번(누름 + 뗌 같은 자리) */ export const click = (x: number, y: number, mods: Mods = {}) => { press(x, y, mods); release(x, y, mods); }; /** a 에서 눌러 b 에서 뗌 */ export const drag = (a: [number, number], b: [number, number], mods: Mods = {}) => { press(a[0], a[1], mods); move(b[0], b[1]); release(b[0], b[1], mods); }; export const rightClick = (shift = false) => controller.handleMouseUp(mouse(0, 0, 2, { shiftKey: shift })); /** 가운데 단추 두 번 */ export const middleDouble = (x: number, y: number) => { for (let i = 0; i < 2; i++) { controller.handleMouseDown(mouse(x, y, 1)); controller.handleMouseUp(mouse(x, y, 1)); } }; /** 도면 객체(유형 이름 거름 — 「Line」 · 「Circle」 …) */ export const entities = (type?: string) => getEntities().filter((entity) => !type || entity.getType() === type); /** 커서 곁 첫 줄(지금 물음) */ export const promptLine = () => currentPrompt().line; /** 커서 곁 둘째 줄(거절 · 안내) */ export const noticeLine = () => currentNotice(); /** 맨 위 도구 이름(Tool) */ export const activeTool = () => getActiveToolActor()?.getSnapshot()?.context?.type; export const toolState = () => getActiveToolActor()?.getSnapshot()?.value; /** 친 글(커서 곁 입력 칸) */ export const inputText = () => (controller as unknown as { text: string }).text; export const tick = () => new Promise((resolve) => setTimeout(resolve, 0));