Files
Aislo/resources/tester/cad/transform-acad.test.ts
T
eomsangdonandClaude Opus 5.5 d35bdfa69d feat(CAD): 337-5 라 옮기기 무리 1 단계 — MOVE · ROTATE · SCALE 오토캐드 물음 차례
- MOVE: 기준점 [변위(D)] <변위> · 두 번째 점 <첫 번째 점을 변위로 사용> · 변위 기억(MOVE.DISP) · 한 번 뒤 끝 · Esc 제자리 끝
- ROTATE · SCALE: 공용 틀(pivot-tool) — 누름 한 번 = 값 · 복사(C) · 참조(R) → 새 값 [점(P)] · 각 · 비율 기억 · 0 이하 축척 거절
- 공용 고르기 상태(transform.helpers) — PICKFIRST · 빈 고르기 Enter = 끝 · OPTION.* 넘김 · 원본은 확정까지 도면에 그대로(N1 · N2)
- 시험 transform-acad(15) · scale-tool 단위 시험 새 상태 이름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
2026-10-16 00:17:16 +09:00

319 lines
9.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 337 라 — 옮기기 무리 7(MOVE · COPY · ROTATE · SCALE · MIRROR · STRETCH · ARRAY) + PEDIT 고르기 · 오토캐드 물음 차례.
* 설계 `docs/raw/조사/2026-10-15_337_CAD오토캐드조작_설계.md` 1-3 · 2-14 · 6-3 라 줄.
* 시험 쪽 flatten-js 는 다른 사본 — 점은 앱 코드(LINE · 친 좌표)로만 만들고 숫자로 판정.
* 실행: cd B07_DesignDetail/openwebcad && npx vitest run --dir ../../resources/tester/cad transform-acad
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("react-toastify", () => ({
toast: Object.assign(vi.fn(), { error: vi.fn(), info: vi.fn(), warn: vi.fn(), success: vi.fn() }),
}));
import { remember } from "../../../B07_DesignDetail/openwebcad/src/commands/prompt";
import { runCommandInput } from "../../../B07_DesignDetail/openwebcad/src/commands/run-command";
import type { Entity } from "../../../B07_DesignDetail/openwebcad/src/entities/Entity";
import {
getSelectedEntityIds,
setSelectedEntityIds,
} from "../../../B07_DesignDetail/openwebcad/src/state";
import { Tool } from "../../../B07_DesignDetail/openwebcad/src/tools";
import {
activeTool,
click,
entities,
initIdle,
key,
move,
noticeLine,
promptLine,
type,
} from "./helpers/cad-drive";
type Xy = { x: number; y: number };
const r = (value: number) => Math.round(value * 1000) / 1000 + 0;
const ends = (entity: Entity) => {
const line = entity as unknown as { getStartPoint(): Xy; getEndPoint(): Xy };
const a = line.getStartPoint();
const b = line.getEndPoint();
return [r(a.x), r(a.y), r(b.x), r(b.y)];
};
/** 선들 끝점(정렬) */
const lineEnds = () =>
entities("Line")
.map(ends)
.sort((p, q) => p[0] - q[0] || p[1] - q[1] || p[2] - q[2] || p[3] - q[3]);
const enter = () => key("Enter");
const esc = () => key("Escape");
/** 선 하나 그림(쉼으로 돌아옴) */
const line = (a: string, b: string) => {
type("l");
type(a);
type(b);
esc();
};
/** 선 그리고 고름 */
const lineSelected = (a = "0,0", b = "20,0") => {
line(a, b);
setSelectedEntityIds(entities().map((entity) => entity.id));
};
/** 세션 기억 — 시험마다 처음 값으로 */
const KEYS = [
"MOVE.DISP",
"STRETCH.DISP",
"COPYMODE",
"ROTATE.ANGLE",
"ROTATE.REF",
"ROTATE.NEW",
"SCALE.FACTOR",
"SCALE.REF",
"SCALE.NEW",
"ARRAYTYPE",
"ARRAY.COLS",
"ARRAY.ROWS",
"ARRAY.ITEMS",
"ARRAY.FILL",
];
beforeEach(() => {
initIdle();
for (const name of KEYS) remember(name, undefined);
});
describe("MOVE(1-3)", () => {
it("물음 차례 · 두 번째 점 = 기준점에서 변위 · 한 번 뒤 끝 · 고름 풂", () => {
lineSelected();
const id = entities()[0].id;
type("m");
expect(promptLine()).toBe("기준점 지정 또는 [변위(D)] <변위>:");
type("0,0");
expect(promptLine()).toBe("두 번째 점 지정 또는 <첫 번째 점을 변위로 사용>:");
type("@10,5");
expect(lineEnds()).toEqual([[10, 5, 30, 5]]);
expect(entities()[0].id).toBe(id);
expect(activeTool()).toBe(Tool.SELECT);
expect(getSelectedEntityIds()).toEqual([]);
});
it("빈 Enter 두 번째 점 = 첫 점 좌표가 변위", () => {
lineSelected();
type("move");
type("3,4");
enter();
expect(lineEnds()).toEqual([[3, 4, 23, 4]]);
expect(activeTool()).toBe(Tool.SELECT);
});
it("D · 기준점 빈 Enter = <변위> · 변위 기억(MOVE.DISP)", () => {
lineSelected();
type("m");
type("d");
expect(promptLine()).toBe("변위 지정 <0.0000, 0.0000>:");
type("10,5");
expect(lineEnds()).toEqual([[10, 5, 30, 5]]);
setSelectedEntityIds(entities().map((entity) => entity.id));
type("m");
enter(); // 기준점 빈 Enter = 변위(D)
expect(promptLine()).toBe("변위 지정 <10.0000, 5.0000>:");
enter(); // 기억한 변위
expect(lineEnds()).toEqual([[20, 10, 40, 10]]);
});
it("Esc = 제자리 끝(기준점 뒤 · 미리보기 중)", () => {
lineSelected();
type("m");
type("0,0");
move(50, 50);
esc();
expect(lineEnds()).toEqual([[0, 0, 20, 0]]);
expect(activeTool()).toBe(Tool.SELECT);
expect(getSelectedEntityIds()).toEqual([]);
});
it("고른 것 없음 → 「객체 선택:」 · 누름 고름 · Enter → 기준점 · 빈 고르기 Enter = 끝", () => {
line("0,0", "20,0");
type("m");
expect(promptLine()).toBe("객체 선택:");
enter();
expect(activeTool()).toBe(Tool.SELECT);
type("m");
click(10, 0);
enter();
expect(promptLine()).toBe("기준점 지정 또는 [변위(D)] <변위>:");
type("0,0");
type("0,10");
expect(lineEnds()).toEqual([[0, 10, 20, 10]]);
});
it("점 묻는 중 명령 이름 = 거절(도구 그대로)", () => {
lineSelected();
type("m");
type("0,0");
type("c");
expect(noticeLine()).toBe("점 또는 옵션 키워드를 요구함.");
expect(activeTool()).toBe(Tool.MOVE);
});
it("N1 — 기준점 뒤 딴 명령 · Ctrl+Z → 원본 제자리 · 두 벌 없음", () => {
lineSelected();
const id = entities()[0].id;
type("m");
type("0,0");
move(40, 40);
runCommandInput("CIRCLE");
expect(activeTool()).toBe(Tool.CIRCLE);
expect(lineEnds()).toEqual([[0, 0, 20, 0]]);
expect(entities()[0].id).toBe(id);
esc();
setSelectedEntityIds([id]);
type("m");
type("0,0");
key("z", { ctrlKey: true, code: "KeyZ" });
expect(activeTool()).toBe(Tool.SELECT);
expect(entities().filter((entity) => entity.id === id)).toHaveLength(
entities().length > 0 ? 1 : 0,
);
});
});
describe("ROTATE(1-3)", () => {
it("물음 차례 · 숫자 = 반시계 + · 한 번 뒤 끝 · 각 기억", () => {
lineSelected("10,0", "20,0");
type("ro");
expect(promptLine()).toBe("기준점 지정:");
type("0,0");
expect(promptLine()).toBe("회전 각도 지정 또는 [복사(C)/참조(R)] <0>:");
type("90");
expect(lineEnds()).toEqual([[0, 10, 0, 20]]);
expect(activeTool()).toBe(Tool.SELECT);
setSelectedEntityIds(entities().map((entity) => entity.id));
type("ro");
type("0,0");
expect(promptLine()).toBe("회전 각도 지정 또는 [복사(C)/참조(R)] <90>:");
enter(); // 기억한 90
expect(lineEnds()).toEqual([[-10, 0, -20, 0]]);
});
it("누름 한 번 = 기준점에서 그 점 방향 각", () => {
lineSelected("10,0", "20,0");
type("ro");
type("0,0");
type("-30,30"); // 135°
const [x1, y1] = lineEnds()[0];
expect([x1, y1]).toEqual([r(-10 / Math.SQRT2), r(10 / Math.SQRT2)]);
});
it("C = 사본 회전(원본 둠)", () => {
lineSelected("10,0", "20,0");
type("ro");
type("0,0");
type("c");
expect(activeTool()).toBe(Tool.ROTATE);
type("90");
expect(lineEnds()).toEqual([
[0, 10, 0, 20],
[10, 0, 20, 0],
]);
});
it("R — 참조 두 점(@ = 기준점) → 새 각도 · 참조 숫자 · P 두 점", () => {
lineSelected("10,0", "20,0");
type("ro");
type("0,0");
type("r");
expect(promptLine()).toBe("참조 각도를 지정 <0>:");
type("@0,0"); // 기준점
expect(promptLine()).toBe("두 번째 점 지정:");
type("10,10"); // 참조 45°
expect(promptLine()).toBe("새 각도 지정 또는 [점(P)] <0>:");
type("90"); // 90 − 45
const s = 10 / Math.SQRT2;
expect(lineEnds()).toEqual([[r(s), r(s), r(2 * s), r(2 * s)]]);
setSelectedEntityIds(entities().map((entity) => entity.id));
type("ro");
type("0,0");
type("r");
expect(promptLine()).toBe("참조 각도를 지정 <45>:");
type("45");
type("p");
expect(promptLine()).toBe("첫 번째 점 지정:");
type("0,0");
type("-5,0"); // 새 180 → 135 돌림
expect(lineEnds()).toEqual([[-10, 0, -20, 0]]);
});
it("Esc = 제자리 끝 · 글 = 각 거절", () => {
lineSelected("10,0", "20,0");
type("ro");
type("0,0");
type("abc");
expect(noticeLine()).toBe("유효한 숫자 각도, 두 번째 점 또는 옵션 키워드를 요구함.");
move(0, 40);
esc();
expect(lineEnds()).toEqual([[10, 0, 20, 0]]);
expect(activeTool()).toBe(Tool.SELECT);
});
});
describe("SCALE(1-3)", () => {
it("물음 차례 · 숫자 비율 · 점 = 거리 · 기억", () => {
lineSelected();
type("sc");
expect(promptLine()).toBe("기준점 지정:");
type("0,0");
expect(promptLine()).toBe("축척 비율 지정 또는 [복사(C)/참조(R)] <1.0000>:");
type("2");
expect(lineEnds()).toEqual([[0, 0, 40, 0]]);
expect(activeTool()).toBe(Tool.SELECT);
setSelectedEntityIds(entities().map((entity) => entity.id));
type("sc");
type("0,0");
expect(promptLine()).toBe("축척 비율 지정 또는 [복사(C)/참조(R)] <2.0000>:");
type("0,0.5"); // 거리 0.5
expect(lineEnds()).toEqual([[0, 0, 20, 0]]);
});
it("0 · 음수 거절(도구 그대로) · C = 사본", () => {
lineSelected();
type("sc");
type("0,0");
type("0");
expect(noticeLine()).toBe("값은 양수이고 0이 아니어야 함.");
type("-2");
expect(noticeLine()).toBe("값은 양수이고 0이 아니어야 함.");
expect(activeTool()).toBe(Tool.SCALE);
type("c");
type("0.5");
expect(lineEnds()).toEqual([
[0, 0, 10, 0],
[0, 0, 20, 0],
]);
});
it("R — 참조 숫자 → 새 길이 · 참조 두 점 → P 두 점", () => {
lineSelected();
type("sc");
type("0,0");
type("r");
expect(promptLine()).toBe("참조 길이 지정 <1.0000>:");
type("10");
expect(promptLine()).toBe("새 길이 지정 또는 [점(P)] <1.0000>:");
type("5");
expect(lineEnds()).toEqual([[0, 0, 10, 0]]);
setSelectedEntityIds(entities().map((entity) => entity.id));
type("sc");
type("0,0");
type("r");
type("0,0");
type("10,0"); // 참조 10
type("p");
type("0,0");
type("0,30"); // 새 30
expect(lineEnds()).toEqual([[0, 0, 30, 0]]);
});
});