Files
Aislo/resources/tester/cad/modify-acad.test.ts
T
eomsangdonandClaude Opus 5.5 00a379595b feat(CAD): 337-4a 다-1 고치기 7 — OFFSET · FILLET · CHAMFER · TRIM · EXTEND · BREAK · JOIN 오토캐드 물음 차례 · 옵션 · 기본값 기억 · 빈 Enter · Esc · 끝난 뒤
- OFFSET 새 파일(offset-tool.ts) — 거리 <통과점> · 통과점(T) · 지우기(E) 예/아니오 · 객체 → 면의 점 되풀이 · 다중(M) <다음 객체> · 명령 취소(U) · 기억 OFFSETDIST · OFFSETERASE
- FILLET · CHAMFER 한 꼴 — 객체 먼저 · 반지름(R) · 거리(D) 둘(둘째 기본 = 첫째) · 자르기(T) 모드 · 다중(M) · 명령 취소(U) · Shift = 구석 · 기억 FILLETRAD · CHAMFERA/B · TRIMMODE
- TRIM · EXTEND 빠른 모드 한 꼴 — 되풀이 · Shift = 반대 일 · 집기 반경 밖 무시 · 지우기(R) · 숨은 U · 사각형 변 원래 층 · 꼴(N9) · 자른 선 꼴 유지
- BREAK — 누른 점 = 첫 끊기점 · 첫 번째 점(F) · @0,0 = 같은 점 · JOIN 물음 글
- 시험 resources/tester/cad/modify-acad.test.ts 13

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

375 lines
12 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 다-1 고치기 7 — OFFSET · FILLET · CHAMFER · TRIM · EXTEND · BREAK · JOIN 을 오토캐드 물음 차례 · 옵션 · 기본값 ·
* 빈 Enter · Esc · 끝난 뒤 동작으로(설계 `docs/raw/조사/2026-10-15_337_CAD오토캐드조작_설계.md` 1-2 · 6-3 다 줄).
* 기억(OFFSETDIST · FILLETRAD · CHAMFERA/B · TRIMMODE)은 파일 안 세션 하나 — 시험 차례대로 이어짐.
* 실행: cd B07_DesignDetail/openwebcad && npx vitest run --dir ../../resources/tester/cad modify-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 { Tool } from "../../../B07_DesignDetail/openwebcad/src/tools";
import { activeTool, click, entities, initIdle, key, promptLine, type } from "./helpers/cad-drive";
type Shape = { start: { x: number; y: number }; end: { x: number; y: number } };
const enter = () => key("Enter");
const r = (value: number) => Math.round(value * 1e4) / 1e4;
/** 선 하나 — 쉼에서 그리고 Esc 로 끝 */
const line = (a: string, b: string) => {
type("l");
type(a);
type(b);
key("Escape");
};
/** 선마다 [x1, y1, x2, y2] — 차례 무관(정렬) */
const segs = () =>
entities("Line")
.map((entity) => {
const { start, end } = entity.getShape() as unknown as Shape;
return [r(start.x), r(start.y), r(end.x), r(end.y)];
})
.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2] || a[3] - b[3]);
const ys = () =>
segs()
.map((s) => s[1])
.sort((a, b) => a - b);
beforeEach(() => {
initIdle();
});
describe("OFFSET", () => {
it("처음 <통과점> · 통과점 · 되풀이 · 거리 기억 · T · 빈 Enter 끝", () => {
line("0,0", "100,0");
type("o");
expect(activeTool()).toBe(Tool.OFFSET);
expect(promptLine()).toBe("간격띄우기 거리 지정 또는 [통과점(T)/지우기(E)] <통과점>:");
enter(); // <통과점>
expect(promptLine()).toBe("간격띄우기할 객체 선택 또는 [종료(E)/명령 취소(U)] <종료>:");
click(50, 0);
expect(promptLine()).toBe("통과점 지정 또는 [종료(E)/다중(M)/명령 취소(U)] <종료>:");
type("30,20");
expect(ys()).toEqual([0, 20]);
expect(promptLine()).toBe("간격띄우기할 객체 선택 또는 [종료(E)/명령 취소(U)] <종료>:"); // 되풀이
key("Escape");
expect(activeTool()).toBe(Tool.SELECT);
type("o");
expect(promptLine()).toBe("간격띄우기 거리 지정 또는 [통과점(T)/지우기(E)] <통과점>:"); // 통과점 기억
type("0");
expect(activeTool()).toBe(Tool.OFFSET); // 0 거절
type("5");
click(50, 0);
expect(promptLine()).toBe(
"간격띄우기할 면의 점 지정 또는 [종료(E)/다중(M)/명령 취소(U)] <종료>:",
);
type("50,-10");
click(50, 20);
type("50,30");
expect(ys()).toEqual([-5, 0, 20, 25]);
enter(); // <종료>
expect(activeTool()).toBe(Tool.SELECT);
type("o");
expect(promptLine()).toBe("간격띄우기 거리 지정 또는 [통과점(T)/지우기(E)] <5.0000>:");
type("t");
click(50, 25);
expect(promptLine()).toBe("통과점 지정 또는 [종료(E)/다중(M)/명령 취소(U)] <종료>:");
type("e"); // 종료
expect(activeTool()).toBe(Tool.SELECT);
type("o");
expect(promptLine()).toBe("간격띄우기 거리 지정 또는 [통과점(T)/지우기(E)] <통과점>:");
key("Escape");
});
it("지우기(E) 예 · 다중(M) · 명령 취소(U) · 없는 옵션 L 안내", () => {
line("0,0", "100,0");
type("o");
type("l");
expect(activeTool()).toBe(Tool.OFFSET); // 도면층(L) = 이 CAD 에 없음
type("e");
expect(promptLine()).toBe(
"간격띄우기 후 원본 객체를 지우시겠습니까? [예(Y)/아니오(N)] <아니오>:",
);
type("y");
type("10");
click(50, 0);
type("50,5");
expect(ys()).toEqual([10]); // 원본 지움
key("Escape");
type("o");
type("e");
expect(promptLine()).toBe("간격띄우기 후 원본 객체를 지우시겠습니까? [예(Y)/아니오(N)] <예>:");
type("n");
enter(); // <10.0000>
click(50, 10);
type("m");
expect(promptLine()).toBe("간격띄우기할 면의 점 지정 또는 [종료(E)/명령 취소(U)] <다음 객체>:");
type("50,100");
type("50,100");
expect(ys()).toEqual([10, 20, 30]); // 방금 만든 것에서 되풀이 · 원본 둠
type("u");
expect(ys()).toEqual([10, 20]);
expect(promptLine()).toBe("간격띄우기할 객체 선택 또는 [종료(E)/명령 취소(U)] <종료>:");
type("u");
type("u"); // 이 명령에서 만든 것 없음 = 그대로
expect(ys()).toEqual([10]);
enter();
expect(activeTool()).toBe(Tool.SELECT);
});
});
/** ㄱ 자 — 가로 (0,0)~(100,0) · 세로 (80,−20)~(80,100) · 구석 (80, 0) */
const corner = () => {
line("0,0", "100,0");
line("80,-20", "80,100");
};
describe("FILLET", () => {
it("물음 글 · R 기억 · 쌍 하나 뒤 끝", () => {
corner();
type("f");
expect(promptLine()).toBe("첫 번째 객체 선택 또는 [명령 취소(U)/반지름(R)/자르기(T)/다중(M)]:");
type("r");
expect(promptLine()).toBe("모깎기 반지름 지정 <0.0000>:");
type("10");
expect(promptLine()).toBe("첫 번째 객체 선택 또는 [명령 취소(U)/반지름(R)/자르기(T)/다중(M)]:");
click(30, 0);
expect(promptLine()).toBe(
"두 번째 객체 선택 또는 Shift 키를 누른 채 선택하여 구석 적용 또는 [반지름(R)]:",
);
click(80, 60);
expect(segs()).toEqual([
[0, 0, 70, 0],
[80, 100, 80, 10],
]);
expect(entities("Arc")).toHaveLength(1);
expect(activeTool()).toBe(Tool.SELECT);
type("f");
type("r");
expect(promptLine()).toBe("모깎기 반지름 지정 <10.0000>:");
key("Escape");
expect(activeTool()).toBe(Tool.SELECT);
});
it("다중(M) · Shift = 반지름 0 구석 · 명령 취소(U) · 자르기(T) 자르지 않기 · 빈 Enter 끝", () => {
corner();
const before = segs();
type("f");
type("m");
click(30, 0);
click(80, 60, { shiftKey: true });
expect(segs()).toEqual([
[0, 0, 80, 0],
[80, 100, 80, 0],
]);
expect(entities("Arc")).toHaveLength(0);
expect(promptLine()).toBe("첫 번째 객체 선택 또는 [명령 취소(U)/반지름(R)/자르기(T)/다중(M)]:"); // 다중 되풀이
type("u");
expect(segs()).toEqual(before);
type("t");
expect(promptLine()).toBe("자르기 모드 옵션 입력 [자르기(T)/자르지 않기(N)] <자르기>:");
type("n");
click(30, 0);
click(80, 60);
expect(segs()).toEqual(before); // 안 자름
expect(entities("Arc")).toHaveLength(1);
enter();
expect(activeTool()).toBe(Tool.SELECT);
type("f");
type("t");
expect(promptLine()).toBe("자르기 모드 옵션 입력 [자르기(T)/자르지 않기(N)] <자르지 않기>:");
type("t");
key("Escape");
});
});
describe("CHAMFER", () => {
it("D 기억(둘째 기본 = 첫째) · 모따기 선 · 한 번 뒤 끝", () => {
corner();
type("cha");
expect(activeTool()).toBe(Tool.CHAMFER);
expect(promptLine()).toBe("첫 번째 선 선택 또는 [명령 취소(U)/거리(D)/자르기(T)/다중(M)]:");
type("d");
expect(promptLine()).toBe("첫 번째 모따기 거리 지정 <0.0000>:");
type("10");
expect(promptLine()).toBe("두 번째 모따기 거리 지정 <10.0000>:");
enter();
click(30, 0);
expect(promptLine()).toBe(
"두 번째 선 선택 또는 Shift 키를 누른 채 선택하여 구석 적용 또는 [거리(D)]:",
);
click(80, 60);
expect(segs()).toEqual([
[0, 0, 70, 0],
[70, 0, 80, 10],
[80, 100, 80, 10],
]);
expect(activeTool()).toBe(Tool.SELECT);
type("cha");
type("d");
expect(promptLine()).toBe("첫 번째 모따기 거리 지정 <10.0000>:");
type("4");
expect(promptLine()).toBe("두 번째 모따기 거리 지정 <4.0000>:");
key("Escape");
});
it("Shift = 거리 0 구석", () => {
corner();
type("cha");
click(30, 0);
click(80, 60, { shiftKey: true });
expect(segs()).toEqual([
[0, 0, 80, 0],
[80, 100, 80, 0],
]);
});
});
describe("TRIM", () => {
/** 가로 (0,0)~(100,0) · 세로 둘 x = 30 · 70 */
const fence = () => {
line("0,0", "100,0");
line("30,-20", "30,20");
line("70,-20", "70,20");
};
it("되풀이 · 반경 밖 무시 · U · 지우기(R) · 빈 Enter 끝", () => {
fence();
type("tr");
expect(activeTool()).toBe(Tool.ERASER);
expect(promptLine()).toBe(
"자를 객체 선택 또는 Shift 키를 누른 채 선택하여 연장 또는 [지우기(R)]:",
);
click(50, 0);
expect(segs()).toContainEqual([0, 0, 30, 0]);
expect(segs()).toContainEqual([70, 0, 100, 0]);
expect(segs()).toHaveLength(4);
click(50, 50); // 집기 반경 밖
expect(segs()).toHaveLength(4);
type("u");
expect(segs()).toContainEqual([0, 0, 100, 0]);
click(50, 0);
click(30, 15);
expect(segs()).toContainEqual([30, -20, 30, 0]);
expect(activeTool()).toBe(Tool.ERASER); // 되풀이
type("r");
expect(promptLine()).toBe("지울 객체 선택:");
click(70, 10);
enter();
expect(segs().some((s) => s[0] === 70 && s[2] === 70)).toBe(false);
expect(promptLine()).toBe(
"자를 객체 선택 또는 Shift 키를 누른 채 선택하여 연장 또는 [지우기(R)]:",
);
type("c");
expect(activeTool()).toBe(Tool.ERASER); // 걸치기(C) = 이 CAD 에 없음
enter();
expect(activeTool()).toBe(Tool.SELECT);
});
it("Esc 한 번 = 끝 · Shift = 연장", () => {
line("0,0", "50,0");
line("80,-20", "80,20");
type("tr");
click(40, 0, { shiftKey: true });
expect(segs()).toContainEqual([0, 0, 80, 0]);
key("Escape");
expect(activeTool()).toBe(Tool.SELECT);
});
it("사각형 변 = 원래 도면층 · 꼴(N9)", () => {
type("rec");
type("0,0");
type("100,40");
key("Escape");
const rectangle = entities("Rectangle")[0];
rectangle.layerId = "L2";
rectangle.lineColor = "#ff0000";
line("20,-10", "20,50");
type("tr");
click(60, 40);
const sides = entities("Line").filter((entity) => entity.layerId === "L2");
expect(sides.length).toBe(4);
expect(sides.every((entity) => entity.lineColor === "#ff0000")).toBe(true);
expect(segs()).toContainEqual([0, 40, 20, 40]);
});
});
describe("EXTEND", () => {
it("빠른 연장 · 되풀이 · Shift = 자르기 · 빈 Enter 끝", () => {
line("0,0", "50,0");
line("80,-20", "80,20");
type("ex");
expect(activeTool()).toBe(Tool.EXTEND);
expect(promptLine()).toBe("연장할 객체 선택 또는 Shift 키를 누른 채 선택하여 자르기:");
click(40, 0);
expect(segs()).toContainEqual([0, 0, 80, 0]);
click(80, 10, { shiftKey: true });
expect(segs()).toContainEqual([80, -20, 80, 0]);
expect(activeTool()).toBe(Tool.EXTEND);
enter();
expect(activeTool()).toBe(Tool.SELECT);
});
});
describe("BREAK", () => {
it("누른 점 = 첫 끊기점 · 둘째 점 · 한 번 뒤 끝", () => {
line("0,0", "100,0");
type("br");
expect(promptLine()).toBe("객체 선택:");
click(20, 0);
expect(promptLine()).toBe("두 번째 끊기점 지정 또는 [첫 번째 점(F)]:");
type("60,0");
expect(segs()).toEqual([
[0, 0, 20, 0],
[60, 0, 100, 0],
]);
expect(activeTool()).toBe(Tool.SELECT);
});
it("첫 번째 점(F) · `@0,0` = 같은 점(틈 없이 둘)", () => {
line("0,0", "100,0");
type("br");
click(50, 0);
type("f");
expect(promptLine()).toBe("첫 번째 끊기점 지정:");
type("10,0");
expect(promptLine()).toBe("두 번째 끊기점 지정:");
type("30,0");
expect(segs()).toEqual([
[0, 0, 10, 0],
[30, 0, 100, 0],
]);
type("br");
click(60, 0);
type("@0,0");
expect(segs()).toEqual([
[0, 0, 10, 0],
[30, 0, 60, 0],
[60, 0, 100, 0],
]);
});
});
describe("JOIN", () => {
it("고르기 물음 글 · 빈 Enter = 결합 · 한 번 뒤 끝", () => {
line("0,0", "40,0");
line("40,0", "40,40");
type("j");
expect(promptLine()).toBe("한 번에 결합할 원본 객체 또는 여러 객체 선택:");
click(20, 0);
click(40, 20);
enter();
expect(entities("Line")).toHaveLength(0);
expect(entities("PolyLine")).toHaveLength(1);
expect(activeTool()).toBe(Tool.SELECT);
});
});