/** * 기준점 십자(좌표 0,0) 검증 — 확대해도 화면 크기 고정 · 원점에 붙음 · 도면 개체를 안 늘림. * 실행: cd B07_DesignDetail/openwebcad && npx vitest run --config vitest.tmp.config.ts origin-marker */ import { Point } from '@flatten-js/core'; import { describe, expect, it, vi } from 'vitest'; import { ScreenCanvasDrawController } from '../../../B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController'; import { drawOriginMarker } from '../../../B07_DesignDetail/openwebcad/src/helpers/draw-functions'; import { getEntities } from '../../../B07_DesignDetail/openwebcad/src/state'; // 자동색(theme.ts)이 `document` 의 data-theme 를 읽는다. vi.stubGlobal('document', { documentElement: { getAttribute: () => 'dark' } }); /** moveTo·lineTo·arc 인자만 기록하는 최소 2d 컨텍스트 — 나머지 호출은 무시. */ function recordingContext() { const segments: number[][] = []; const arcs: number[][] = []; let from: number[] = []; const context = new Proxy( { lineWidth: 1, moveTo: (x: number, y: number) => (from = [x, y]), lineTo: (x: number, y: number) => segments.push([...from, x, y]), arc: (x: number, y: number, r: number) => arcs.push([x, y, r]), } as Record, { get: (target, key: string) => target[key] ?? (() => undefined), set: () => true } ) as unknown as CanvasRenderingContext2D; return { context, segments, arcs }; } function drawAt(scale: number, offset: Point) { const { context, segments, arcs } = recordingContext(); const controller = new ScreenCanvasDrawController(context); controller.setCanvasSize(new Point(800, 600)); controller.setScreenScale(scale); controller.setScreenOffset(offset); drawOriginMarker(controller); const origin = controller.worldToTarget(new Point(0, 0)); return { segments, arcs, origin }; } describe('기준점 십자', () => { it.each([0.01, 1, 40])('확대 %s 배에서도 가로세로 20px', (scale) => { const { segments, arcs } = drawAt(scale, new Point(-120, 75)); expect(segments).toHaveLength(2); const lengths = segments.map(([x1, y1, x2, y2]) => Math.hypot(x2 - x1, y2 - y1)); expect(lengths).toEqual([20, 20]); // 끝점 둥글림(반지름 0.5)을 뺀 원 하나 — 작은 원 expect(arcs.filter(([, , r]) => r > 1).map(([, , r]) => r)).toEqual([10 / 3]); }); it('십자 중심이 원점에 붙어 이동을 따라감', () => { const a = drawAt(2, new Point(0, 0)); const b = drawAt(2, new Point(50, -30)); const center = ([x1, , x2]: number[]) => (x1 + x2) / 2; expect(center(a.segments[0])).toBeCloseTo(a.origin.x); expect(center(b.segments[0])).toBeCloseTo(b.origin.x); expect(b.origin.x - a.origin.x).toBeCloseTo(-100); // 세계 50 = 화면 100px(2배) — 화면을 옮기면 원점이 반대로 감 }); it('빈 도면 맞춤은 원점을 화면 가운데로 — 십자가 귀퉁이에 안 잘림', () => { const { context } = recordingContext(); const controller = new ScreenCanvasDrawController(context); controller.setCanvasSize(new Point(800, 600)); controller.setScreenScale(2); controller.setScreenOffset(new Point(0, 0)); controller.zoomToFitScreen(); const origin = controller.worldToTarget(new Point(0, 0)); expect([origin.x, origin.y]).toEqual([400, 300]); }); it('맞춘 뒤 캔버스가 커지면 다시 맞춤 · 사용자가 옮긴 뒤에는 그대로', () => { const { context } = recordingContext(); const controller = new ScreenCanvasDrawController(context); controller.setCanvasSize(new Point(200, 100)); controller.zoomToFitScreen(); controller.setCanvasSize(new Point(800, 600)); const refit = controller.worldToTarget(new Point(0, 0)); expect([refit.x, refit.y]).toEqual([400, 300]); controller.panScreen(50, 0); controller.setCanvasSize(new Point(1000, 600)); const kept = controller.worldToTarget(new Point(0, 0)); expect([kept.x, kept.y]).toEqual([450, 300]); }); it('그리기만 — 도면 개체를 만들지 않음', () => { drawAt(1, new Point(0, 0)); expect(getEntities()).toHaveLength(0); }); });