Files
Aislo/resources/tester/cad/prompt-contract.test.ts
T
eomsangdonandClaude Opus 5.5 933c4c5d71 refactor(CAD): 337-6 가 끝 몫 — 옛 OFFSET · LENGTHEN · 옛 꼴 통과 지움 · ALL · L · P 실제 고름 시험 · 안내 글
- transform-acad: 고르기 물음 ALL · L · P = 「n개를 찾음, 총 m개」 · 다시 「객체 선택:」 · 빈 Enter = 고르기 끝(설계 1-4 · 2-15)
- transform-tools: 안 쓰는 옛 OFFSET · LENGTHEN 머신 지움(새 offset-tool · lengthen-tool 이 대신)
- prompt-input · prompt · sequence-tool: 옛 꼴 통과(meta.options 글 배열 · TEXT_INPUT 대문자 통과 · chainFromLastPoint) 지움
- prompt-contract: 옛 꼴 안 쓰는지 지키는 시험 더함
- 안내 글(설계 4 장 끝): 오른쪽 단추 · 휠 두 번 · CAD 키 줄

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

77 lines
3.2 KiB
TypeScript

/**
* 337 계약 시험(설계 2-16) — 모든 도구 머신의 평평한 상태가 물음 약속을 지키는지.
* ① `prompt.options` key 마다 `OPTION.<key>` 또는 `OPTION.*` 처리 ② 한 물음 안 kw 안 겹침(없는 옵션 포함)
* ③ `meta.kind` 있는 상태마다 ESC ④ `prompt` 와 옛 `instructions` 를 같이 두지 않음 ⑤ 함수 꼴 물음은 빈 context 로 그려짐.
* 실행: cd B07_DesignDetail/openwebcad && npx vitest run --dir ../../resources/tester/cad prompt-contract
*/
import { 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 {
type PromptMeta,
resolvePrompt,
} from "../../../B07_DesignDetail/openwebcad/src/commands/prompt";
import { TOOL_STATE_MACHINES } from "../../../B07_DesignDetail/openwebcad/src/commands/registry";
type StateNode = { config: { meta?: PromptMeta; on?: Record<string, unknown> } };
const rows = Object.entries(TOOL_STATE_MACHINES).flatMap(([tool, machine]) =>
Object.entries((machine as { states: Record<string, StateNode> }).states).map(([name, node]) => ({
where: `${tool}.${name}`,
meta: node.config.meta,
on: node.config.on ?? {},
})),
);
describe("물음 약속(2-16)", () => {
it("도구 머신을 읽음", () => {
expect(rows.length).toBeGreaterThan(100);
});
it("옵션 처리 · kw 안 겹침 · 함수 꼴은 빈 context 로", () => {
const problems: string[] = [];
for (const { where, meta, on } of rows) {
if (!meta?.prompt) continue;
let spec: ReturnType<typeof resolvePrompt>;
try {
spec = resolvePrompt(meta.prompt, {});
} catch (error) {
problems.push(`${where}: 빈 context 로 못 그림 (${(error as Error).message})`);
continue;
}
for (const option of spec?.options ?? []) {
if (!on[`OPTION.${option.key}`] && !on["OPTION.*"]) {
problems.push(`${where}: OPTION.${option.key} 처리 없음`);
}
}
const kws = [...(spec?.options ?? []), ...(spec?.absent ?? [])].map((option) => option.kw);
const twice = kws.filter((kw, index) => kws.indexOf(kw) !== index);
if (twice.length) problems.push(`${where}: kw 겹침 ${twice.join(",")}`);
}
expect(problems).toEqual([]);
});
it("meta.kind 있는 상태 = ESC 있음 · prompt 와 instructions 같이 없음", () => {
const problems: string[] = [];
for (const { where, meta, on } of rows) {
if (meta?.kind && !on.ESC) problems.push(`${where}: ESC 없음`);
if (meta?.prompt && meta.instructions) problems.push(`${where}: prompt + instructions`);
}
expect(problems).toEqual([]);
});
it("옛 꼴 통과 안 씀 — 옵션 글 배열 없음 · 숫자 없이 TEXT_INPUT 만 받는 상태 없음(글 물음 빼고)", () => {
const problems: string[] = [];
for (const { where, meta, on } of rows) {
if ((meta as { options?: unknown } | undefined)?.options)
problems.push(`${where}: meta.options`);
if (on.TEXT_INPUT && !on.NUMBER_INPUT && meta?.kind !== "text")
problems.push(`${where}: TEXT_INPUT 만`);
}
expect(problems).toEqual([]);
});
});