/** * 그립 편집 검증 (조사표 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(); }); });