⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt` 가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는 시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음. - `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침** - `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시) - 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q` 옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0 ⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
82 lines
3.6 KiB
TypeScript
82 lines
3.6 KiB
TypeScript
/**
|
|
* 그립 편집 검증 (조사표 11절 그립 편집 · 다기능 그립).
|
|
* 실행: cd B07_DesignDetail/openwebcad && npx vitest run --config vitest.tmp.config.ts
|
|
*/
|
|
import { Point, Segment } from '@flatten-js/core';
|
|
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
applyGrip,
|
|
findGripAt,
|
|
getGrips,
|
|
removePolylineVertex,
|
|
} from '../../../B07_DesignDetail/openwebcad/src/helpers/grips';
|
|
import { LineEntity } from '../../../B07_DesignDetail/openwebcad/src/entities/LineEntity';
|
|
import { PolyLineEntity } from '../../../B07_DesignDetail/openwebcad/src/entities/PolyLineEntity';
|
|
import { RectangleEntity } from '../../../B07_DesignDetail/openwebcad/src/entities/RectangleEntity';
|
|
|
|
const line = (x1: number, y1: number, x2: number, y2: number) =>
|
|
new LineEntity('layer-1', new Segment(new Point(x1, y1), new Point(x2, y2)));
|
|
|
|
const box = (entity: { getBoundingBox: () => { xmin: number; ymin: number; xmax: number; ymax: number } }) => {
|
|
const bounds = entity.getBoundingBox();
|
|
return [bounds.xmin, bounds.ymin, bounds.xmax, bounds.ymax];
|
|
};
|
|
|
|
describe('그립', () => {
|
|
it('선은 끝점 2개와 중점 1개를 갖고, 끝점을 옮기면 그 끝만 움직인다', () => {
|
|
const entity = line(0, 0, 100, 0);
|
|
const grips = getGrips(entity);
|
|
expect(grips.map((grip) => grip.kind)).toEqual(['vertex', 'vertex', 'midpoint']);
|
|
expect(grips[2].point.x).toBe(50);
|
|
|
|
const edited = applyGrip(entity, grips[1], new Point(100, 60));
|
|
expect(edited).toBeTruthy();
|
|
expect(box(edited as LineEntity)).toEqual([0, 0, 100, 60]);
|
|
// 같은 객체로 바뀌어 끼워지도록 id를 물려받는다
|
|
expect((edited as LineEntity).id).toBe(entity.id);
|
|
// 원본은 그대로다
|
|
expect(box(entity)).toEqual([0, 0, 100, 0]);
|
|
});
|
|
|
|
it('선 중점을 옮기면 선 전체가 따라간다', () => {
|
|
const entity = line(0, 0, 100, 0);
|
|
const edited = applyGrip(entity, getGrips(entity)[2], new Point(50, 40));
|
|
expect(box(edited as LineEntity)).toEqual([0, 40, 100, 40]);
|
|
});
|
|
|
|
it('사각형 모서리를 끌면 마주 보는 모서리를 잡은 채 크기가 바뀐다', () => {
|
|
const entity = new RectangleEntity('layer-1', new Point(0, 0), new Point(100, 50));
|
|
const corner = getGrips(entity).find((grip) => grip.point.x === 0 && grip.point.y === 0);
|
|
expect(corner).toBeTruthy();
|
|
const edited = applyGrip(entity, corner as never, new Point(-20, -10));
|
|
expect(box(edited as RectangleEntity)).toEqual([-20, -10, 100, 50]);
|
|
});
|
|
|
|
it('폴리선 세그먼트 중점을 끌면 정점이 하나 늘고, Ctrl 제거로 다시 줄어든다', () => {
|
|
const polyline = new PolyLineEntity('layer-1', [line(0, 0, 100, 0), line(100, 0, 200, 0)]);
|
|
const grips = getGrips(polyline);
|
|
expect(grips.filter((grip) => grip.kind === 'vertex')).toHaveLength(3);
|
|
|
|
const firstMidpoint = grips.find((grip) => grip.kind === 'midpoint');
|
|
const grown = applyGrip(polyline, firstMidpoint as never, new Point(50, 30));
|
|
expect(getGrips(grown as PolyLineEntity).filter((grip) => grip.kind === 'vertex')).toHaveLength(
|
|
4
|
|
);
|
|
expect(box(grown as PolyLineEntity)).toEqual([0, 0, 200, 30]);
|
|
|
|
const middleVertex = getGrips(grown as PolyLineEntity).filter(
|
|
(grip) => grip.kind === 'vertex'
|
|
)[1];
|
|
const shrunk = removePolylineVertex(grown as PolyLineEntity, middleVertex);
|
|
expect(
|
|
getGrips(shrunk as PolyLineEntity).filter((grip) => grip.kind === 'vertex')
|
|
).toHaveLength(3);
|
|
});
|
|
|
|
it('클릭 지점 가까이에 그립이 없으면 아무것도 집지 않는다', () => {
|
|
const entity = line(0, 0, 100, 0);
|
|
expect(findGripAt([entity], new Point(50, 0), 5)).toBeTruthy(); // 중점
|
|
expect(findGripAt([entity], new Point(50, 40), 5)).toBeNull();
|
|
});
|
|
});
|