From 793e1aad37878f6cccf47e45582e416932fb35ac Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 2 Sep 2026 08:00:39 +0900 Subject: [PATCH] =?UTF-8?q?fix(B07):=20=ED=91=9C=EC=A0=9C=EB=9E=80=20?= =?UTF-8?q?=EA=B0=92=20=EA=B3=B5=EA=B8=89=20=ED=86=B5=EB=A1=9C=20+=20CAD?= =?UTF-8?q?=20=ED=9A=8C=EA=B7=80=206=EA=B1=B4=20=ED=95=B4=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 표제란(도각) 값 공급 - `use_title_fields()` 문맥 변수 신설 — 회사 도각 폴더와 같은 방식이라 작도 엔진 6개의 서명을 고치지 않음. `frame_entities` 가 문맥값 위에 호출부 값(도면명)을 얹음. - 라우터가 도면 한 건을 그리기 전에 DB 값을 세움: 공사명·위치(projects), 용역회사(companies), 설계자(users). 못 채운 자리는 빈칸 — 도각 원본의 남의 값이 도면으로 나가지 않음. - 미공급 항목과 사유를 `_title_block_fields` 주석에 명시: 시행청·과업책임자· 분야별책임자는 DB 칸 부재(B02 마이그레이션 대기), 축척·사업량·연도기번은 임의 수치 금지, 설계일자는 채울 시점 정의 미결. CAD 회귀 6건 (87건 중 81 passed → 87 passed) - `window` 부재 5건: `notifyWindow()` 한 곳으로 모으고 `window` 없는 Node 시험에서는 통지를 건너뜀. `triggerReactUpdate` 가 이미 같은 이유로 시험을 건너뛰던 것과 같은 결. - `find-closest-entity`: 목의 호가 90°까지만 돌아 클릭점에서 147px 떨어져 있었음 — 시험 의도(호가 가장 가깝다)대로 3/4바퀴로 되돌림. - 선색 기대값 4건: 앱 기본 선색이 흰 종이 배경에 맞춰 검정으로 바뀐 뒤 상류 기대값 `#fff` 가 남아 있었음 — `getActiveLineColor()` 대조로 교체. - 좌표 NaN: 없어진 `TOOLBAR_WIDTH` 를 시험 보조가 아직 참조 — 입력 컨트롤러가 캔버스 bounding rect 를 쓰도록 바뀐 현행에 맞춤. - 호 각도 비교: 같은 각의 음수·2π 표기 차이라 [0, 2π) 정규화 후 대조. 검증: `npx vitest run` 87 passed / 0 failed, `npm run check-types`·`npm run build` 통과, `pytest tmp/tests/ -q` 131 passed / 0 failed(신규 `test_b07_title_block_fields.py` 4건 포함). Co-Authored-By: Claude Opus 5 (1M context) --- .../B07_DesignDetail_Engine_Template.py | 13 ++++++- B07_DesignDetail/B07_DesignDetail_Router.py | 36 +++++++++++++++++++ .../openwebcad/src/blocks/block-library.ts | 4 +-- .../openwebcad/src/commands/run-command.ts | 4 +-- .../openwebcad/src/components/ui-state.ts | 3 +- .../src/helpers/find-closest-entity.mocks.ts | 5 +-- .../openwebcad/src/helpers/visibility.ts | 3 +- B07_DesignDetail/openwebcad/src/state.ts | 24 ++++++++----- .../test/entities/circle/circle.test.ts | 4 +-- .../test/entities/line/line.test.ts | 4 +-- .../test/entities/rectangle/rectangle.test.ts | 4 +-- .../openwebcad/test/helpers/click.ts | 4 +-- .../openwebcad/test/helpers/tests.consts.ts | 6 ++-- .../test/tools/eraser/eraser.test.ts | 13 ++++--- 14 files changed, 93 insertions(+), 34 deletions(-) diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Template.py b/B07_DesignDetail/B07_DesignDetail_Engine_Template.py index ff8711a1..b170ca98 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Template.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Template.py @@ -52,6 +52,16 @@ def use_company_templates(company_dir: Path | None) -> None: _company_dir.set(company_dir) +# 이 요청이 도각 표제란에 채울 값. 회사 도각 폴더와 같은 이유로 문맥 변수다 — +# 엔진 6개의 서명을 줄줄이 고치지 않는다. 값을 못 구한 자리는 **빈칸**으로 남는다. +_title_fields: ContextVar[dict[str, str]] = ContextVar("b07_title_fields", default={}) + + +def use_title_fields(fields: dict[str, str] | None) -> None: + """이 요청이 도각 표제란에 채울 값을 정한다. None이면 표제란이 전부 빈칸이다.""" + _title_fields.set(fields or {}) + + def company_template_path(company_dir: Path, name: str = A1_TEMPLATE) -> Path: """회사 도각 파일 경로(없을 수도 있다).""" return Path(company_dir) / COMPANY_TEMPLATE_SUBDIR / f"{name}.json" @@ -307,5 +317,6 @@ def frame_entities( _transform_entity(entity, f"{drawing_id}:frame:{index}", scale, dx, dy) for index, entity in enumerate(template.get("entities", [])) ] - _fill_placeholders(placed, fields or {}) + # 요청 문맥의 값이 바탕, 호출부가 준 값(도면명 등 도면마다 다른 것)이 위에 얹힌다. + _fill_placeholders(placed, {**_title_fields.get(), **(fields or {})}) return placed diff --git a/B07_DesignDetail/B07_DesignDetail_Router.py b/B07_DesignDetail/B07_DesignDetail_Router.py index 6104dd44..a47a1f18 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router.py +++ b/B07_DesignDetail/B07_DesignDetail_Router.py @@ -30,6 +30,7 @@ from B07_DesignDetail.B07_DesignDetail_Engine_Template import ( frame_template_document, save_company_template, use_company_templates, + use_title_fields, ) from B07_DesignDetail.B07_DesignDetail_Router_Support import ( MASS_HAUL_ID, @@ -96,6 +97,39 @@ async def _company_dir(project_id: UUID) -> Path: return root.parent.parent +async def _title_block_fields(project_id: UUID) -> dict[str, str]: + """도각 표제란에 채울 값. **DB가 아는 것만** 담고 나머지는 담지 않는다. + + 담지 않은 자리는 `_fill_placeholders`가 빈칸으로 지운다 — 도각 원본에 남의 값이 + 박혀 있어도 도면에는 나가지 않는다(2026-08-31 사용자 확정, 이것이 1순위 목적). + + 아직 못 채우는 자리와 이유: + - 시행청·과업책임자·분야별책임자 — `projects`에 칸이 없다. B02 등록 화면과 + 마이그레이션이 서야 채워진다(공유 DB 변경이라 사용자 확정 대기). + - 축척(A1/A3)·사업량·연도기번 — 값을 지어내지 않는다(임의 수치 금지). + - 설계일자 — "확정일"인데 도각은 **확정 전**에 그려져 저장본에 굳는다. + 채울 시점 정의가 미결이라 비워 둔다. + """ + pool = get_db_pool() + async with pool.acquire() as connection, connection.cursor() as cursor: + await cursor.execute( + """ + SELECT p.name, p.region, c.name, u.name + FROM projects p + LEFT JOIN companies c ON c.id = p.company_id + LEFT JOIN users u ON u.id = p.user_id + WHERE p.id = %s AND p.deleted_at IS NULL + """, + (str(project_id),), + ) + row = await cursor.fetchone() + if not row: + return {} + name, region, company_name, designer = row + fields = {"공사명": name, "위치": region, "용역회사": company_name, "설계자": designer} + return {key: str(value) for key, value in fields.items() if value} + + async def _designs_by_chainage(route_id: int) -> dict[int, dict[str, Any]]: """노선 전체의 측점별 설계 지정 {측점(m): design}. 장 배치·목록이 함께 쓴다.""" pool = get_db_pool() @@ -142,6 +176,8 @@ async def get_design_drawing( # 이 회사가 고친 도각이 있으면 그것으로 그린다(없으면 프로그램 기본 도각). # 저장 경로는 `storage/{회사}/{사용자}/{프로젝트}` 이므로 두 단계 위가 회사 폴더다. use_company_templates(project_root.parent.parent) + # 표제란 값도 같은 요청 문맥에 세운다 — 값이 없는 칸은 빈칸으로 나간다. + use_title_fields(await _title_block_fields(project_id)) # 횡단도는 B06 지정 설계를 먼저 읽어 CAD 계획선(design_line)과 응답에 함께 쓴다. design: dict[str, Any] | None = None source_design: Any = None diff --git a/B07_DesignDetail/openwebcad/src/blocks/block-library.ts b/B07_DesignDetail/openwebcad/src/blocks/block-library.ts index f820f638..6010be3a 100644 --- a/B07_DesignDetail/openwebcad/src/blocks/block-library.ts +++ b/B07_DesignDetail/openwebcad/src/blocks/block-library.ts @@ -11,7 +11,7 @@ import { HtmlEvent } from '../App.types'; import type { Entity, JsonEntity } from '../entities/Entity'; import { getBoundingBoxOfMultipleEntities } from '../helpers/get-bounding-box-of-multiple-entities'; import { getEntitiesAndLayersFromJsonObject } from '../helpers/import-export-handlers/import-entities-from-json'; -import { getActiveLayerId } from '../state'; +import { getActiveLayerId, notifyWindow } from '../state'; const STORAGE_KEY = 'aislo-cad-block-library'; @@ -24,7 +24,7 @@ export interface BlockDefinition { let blocks: BlockDefinition[] | null = null; -const notify = () => window.dispatchEvent(new CustomEvent(HtmlEvent.UPDATE_STATE)); +const notify = () => notifyWindow(HtmlEvent.UPDATE_STATE); function load(): BlockDefinition[] { if (blocks) return blocks; diff --git a/B07_DesignDetail/openwebcad/src/commands/run-command.ts b/B07_DesignDetail/openwebcad/src/commands/run-command.ts index 3b0a4a86..3c905bb6 100644 --- a/B07_DesignDetail/openwebcad/src/commands/run-command.ts +++ b/B07_DesignDetail/openwebcad/src/commands/run-command.ts @@ -2,7 +2,7 @@ import { toast } from 'react-toastify'; import { Actor } from 'xstate'; import { HtmlEvent } from '../App.types'; -import { getSelectedEntities, isDrawingReadOnly, setActiveToolActor } from '../state'; +import { getSelectedEntities, isDrawingReadOnly, notifyWindow, setActiveToolActor } from '../state'; import type { CadCommand } from './command.types'; import { getCommandById, isViewOnlyCommand, resolveCommandInput } from './registry'; @@ -19,7 +19,7 @@ function log(line: string) { if (commandHistory.length > COMMAND_HISTORY_LIMIT) { commandHistory.splice(0, commandHistory.length - COMMAND_HISTORY_LIMIT); } - window.dispatchEvent(new CustomEvent(HtmlEvent.UPDATE_STATE)); + notifyWindow(HtmlEvent.UPDATE_STATE); } /** 명령 한 건 실행. 도구형이면 도구를 활성화하고, 즉시형이면 run()을 부른다. */ diff --git a/B07_DesignDetail/openwebcad/src/components/ui-state.ts b/B07_DesignDetail/openwebcad/src/components/ui-state.ts index 32462b24..b6a19552 100644 --- a/B07_DesignDetail/openwebcad/src/components/ui-state.ts +++ b/B07_DesignDetail/openwebcad/src/components/ui-state.ts @@ -3,6 +3,7 @@ * 리액트 컴포넌트 바깥에 둔다. 값이 바뀌면 UPDATE_STATE로 다시 그린다. */ import { HtmlEvent } from '../App.types'; +import { notifyWindow } from '../state'; export type InspectorTab = 'properties' | 'layers'; @@ -12,7 +13,7 @@ let quickPropertiesVisible = false; let activeRibbonTab = 'home'; let blockLibraryVisible = false; -const notify = () => window.dispatchEvent(new CustomEvent(HtmlEvent.UPDATE_STATE)); +const notify = () => notifyWindow(HtmlEvent.UPDATE_STATE); export const getInspectorTab = () => inspectorTab; export const isInspectorCollapsed = () => inspectorCollapsed; diff --git a/B07_DesignDetail/openwebcad/src/helpers/find-closest-entity.mocks.ts b/B07_DesignDetail/openwebcad/src/helpers/find-closest-entity.mocks.ts index 13604e27..b29968bc 100644 --- a/B07_DesignDetail/openwebcad/src/helpers/find-closest-entity.mocks.ts +++ b/B07_DesignDetail/openwebcad/src/helpers/find-closest-entity.mocks.ts @@ -33,8 +33,9 @@ export const arcAndLineEntitiesMock: JsonDrawingFileSerialized = { }, radius: 156.92367603040066, startAngle: 0, - // endAngle: (2 * Math.PI * 3) / 4, - endAngle: 1.5707963267948966, + // 클릭점(393,1108)이 호 위(중심각 ≈145°)에 놓이도록 3/4바퀴를 쓴다 — + // 90°까지만 돌면 호가 클릭점에서 147px 떨어져 직선(64px)이 더 가깝다. + endAngle: (2 * Math.PI * 3) / 4, counterClockwise: true, }, }, diff --git a/B07_DesignDetail/openwebcad/src/helpers/visibility.ts b/B07_DesignDetail/openwebcad/src/helpers/visibility.ts index f70854eb..9462b8f0 100644 --- a/B07_DesignDetail/openwebcad/src/helpers/visibility.ts +++ b/B07_DesignDetail/openwebcad/src/helpers/visibility.ts @@ -4,6 +4,7 @@ */ import { HtmlEvent } from '../App.types'; import type { Entity } from '../entities/Entity'; +import { notifyWindow } from '../state'; import { bumpSceneVersion } from './scene-version'; let hiddenEntityIds = new Set(); @@ -14,7 +15,7 @@ export const getHiddenEntityCount = (): number => hiddenEntityIds.size; function apply(ids: Set): void { hiddenEntityIds = ids; bumpSceneVersion(); - window.dispatchEvent(new CustomEvent(HtmlEvent.UPDATE_STATE)); + notifyWindow(HtmlEvent.UPDATE_STATE); } /** 지정 객체를 숨긴다 */ diff --git a/B07_DesignDetail/openwebcad/src/state.ts b/B07_DesignDetail/openwebcad/src/state.ts index 9b2393b3..d8b7d70d 100644 --- a/B07_DesignDetail/openwebcad/src/state.ts +++ b/B07_DesignDetail/openwebcad/src/state.ts @@ -309,9 +309,15 @@ export const setActiveToolActor = ( triggerReactUpdate(StateVariable.activeTool); } }; +/** 화면에 알린다 — `window`가 없는 Node 시험에서는 통지를 건너뛴다. + * (`triggerReactUpdate`가 이미 같은 이유로 시험 환경을 건너뛴다.) */ +export const notifyWindow = (event: HtmlEvent) => { + if (typeof window === 'undefined') return; + window.dispatchEvent(new CustomEvent(event)); +}; export const setLastStateInstructions = (newInstructions: string | null) => { lastStateInstructions = newInstructions; - window.dispatchEvent(new CustomEvent(HtmlEvent.UPDATE_STATE)); + notifyWindow(HtmlEvent.UPDATE_STATE); }; export const setEntities = (newEntities: Entity[], trackInUndoStack = false) => { if (trackInUndoStack) { @@ -321,7 +327,7 @@ export const setEntities = (newEntities: Entity[], trackInUndoStack = false) => bumpSceneVersion(); if (trackInUndoStack) { drawingDirty = true; - window.dispatchEvent(new CustomEvent(HtmlEvent.DRAWING_CHANGED)); + notifyWindow(HtmlEvent.DRAWING_CHANGED); } }; /** 도면을 새로 실었거나 저장했다 — 미저장 표시를 내린다. */ @@ -348,7 +354,7 @@ export const setSelectedEntityIds = (newEntityIds: string[]) => { selectedEntityIds = newEntityIds; selectedEntityIdSet = new Set(newEntityIds); bumpSceneVersion(); // selection style (dashed) is baked into the scene cache - window.dispatchEvent(new CustomEvent(HtmlEvent.UPDATE_STATE)); + notifyWindow(HtmlEvent.UPDATE_STATE); }; export const setShouldDrawCursor = (newValue: boolean) => { shouldDrawCursor = newValue; @@ -462,19 +468,19 @@ export const setSnapEnabled = (enabled: boolean) => { setHoveredSnapPoints([]); setAngleGuideEntities([]); } - window.dispatchEvent(new CustomEvent(HtmlEvent.UPDATE_STATE)); + notifyWindow(HtmlEvent.UPDATE_STATE); }; export const setSnapTrackingEnabled = (enabled: boolean) => { snapTrackingEnabled = enabled; if (!enabled) { setHoveredSnapPoints([]); } - window.dispatchEvent(new CustomEvent(HtmlEvent.UPDATE_STATE)); + notifyWindow(HtmlEvent.UPDATE_STATE); }; export const setGridEnabled = (enabled: boolean) => { gridEnabled = enabled; bumpSceneVersion(); - window.dispatchEvent(new CustomEvent(HtmlEvent.UPDATE_STATE)); + notifyWindow(HtmlEvent.UPDATE_STATE); }; export const setDesignMeta = (newMeta: DesignMeta | null) => { designMeta = newMeta; @@ -556,7 +562,7 @@ export function undo() { updateStates(undoState); drawingDirty = true; // 되돌려도 저장본과는 다를 수 있다 — 미저장 경고 대상이다 - window.dispatchEvent(new CustomEvent(HtmlEvent.DRAWING_CHANGED)); + notifyWindow(HtmlEvent.DRAWING_CHANGED); } export function redo() { @@ -565,7 +571,7 @@ export function redo() { updateStates(redoState); drawingDirty = true; - window.dispatchEvent(new CustomEvent(HtmlEvent.DRAWING_CHANGED)); + notifyWindow(HtmlEvent.DRAWING_CHANGED); } export function triggerReactUpdate(variable: StateVariable) { @@ -577,5 +583,5 @@ export function triggerReactUpdate(variable: StateVariable) { return; } - window.dispatchEvent(new CustomEvent(HtmlEvent.UPDATE_STATE)); + notifyWindow(HtmlEvent.UPDATE_STATE); } diff --git a/B07_DesignDetail/openwebcad/test/entities/circle/circle.test.ts b/B07_DesignDetail/openwebcad/test/entities/circle/circle.test.ts index f911f27e..2ef2e5ed 100644 --- a/B07_DesignDetail/openwebcad/test/entities/circle/circle.test.ts +++ b/B07_DesignDetail/openwebcad/test/entities/circle/circle.test.ts @@ -3,7 +3,7 @@ * Draw a rectangle to the screen and check if the json export contains the correct data using the vitest testing framework */ import { expect, test } from 'vitest'; -import { getEntities } from '../../../src/state'; +import { getActiveLineColor, getEntities } from '../../../src/state'; import { EntityName, type JsonEntity } from '../../../src/entities/Entity'; import { initApplication } from '../../helpers/init-application'; import { CANVAS_HEIGHT } from '../../helpers/tests.consts'; @@ -24,7 +24,7 @@ test('Draw circle', async () => { expect(circleEntity.getType()).toBe(EntityName.Circle); const circleJson = (await circleEntity.toJson()) as JsonEntity; - expect(circleJson.lineColor).toBe('#fff'); + expect(circleJson.lineColor).toBe(getActiveLineColor()); expect(circleJson.lineWidth).toBe(1); expect(circleJson.type).toBe('Circle'); expect(circleJson.shapeData.center.x).toBe(325); diff --git a/B07_DesignDetail/openwebcad/test/entities/line/line.test.ts b/B07_DesignDetail/openwebcad/test/entities/line/line.test.ts index ccfb7bc0..b17cf536 100644 --- a/B07_DesignDetail/openwebcad/test/entities/line/line.test.ts +++ b/B07_DesignDetail/openwebcad/test/entities/line/line.test.ts @@ -3,7 +3,7 @@ * Draw a rectangle to the screen and check if the json export contains the correct data using the vitest testing framework */ import { expect, test } from 'vitest'; -import { getEntities } from '../../../src/state'; +import { getActiveLineColor, getEntities } from '../../../src/state'; import { Tool } from '../../../src/tools'; import { EntityName, type JsonEntity } from '../../../src/entities/Entity'; import { initApplication } from '../../helpers/init-application'; @@ -23,7 +23,7 @@ test('Draw line', async () => { expect(lineEntity.getType()).toBe(EntityName.Line); const lineJson = (await lineEntity.toJson()) as JsonEntity; - expect(lineJson.lineColor).toBe('#fff'); + expect(lineJson.lineColor).toBe(getActiveLineColor()); expect(lineJson.lineWidth).toBe(1); expect(lineJson.type).toBe('Line'); expect(lineJson.shapeData.startPoint.x).toBe(185); diff --git a/B07_DesignDetail/openwebcad/test/entities/rectangle/rectangle.test.ts b/B07_DesignDetail/openwebcad/test/entities/rectangle/rectangle.test.ts index 214e2619..6113e3f0 100644 --- a/B07_DesignDetail/openwebcad/test/entities/rectangle/rectangle.test.ts +++ b/B07_DesignDetail/openwebcad/test/entities/rectangle/rectangle.test.ts @@ -5,7 +5,7 @@ import { expect, test } from 'vitest'; import { EntityName, type JsonEntity } from '../../../src/entities/Entity'; import type { RectangleJsonData } from '../../../src/entities/RectangleEntity'; -import { getEntities } from '../../../src/state'; +import { getActiveLineColor, getEntities } from '../../../src/state'; import { Tool } from '../../../src/tools'; import { click } from '../../helpers/click'; import { initApplication } from '../../helpers/init-application'; @@ -23,7 +23,7 @@ test('Draw circle', async () => { expect(rectangleEntity.getType()).toBe(EntityName.Rectangle); const rectangleJson = (await rectangleEntity.toJson()) as JsonEntity; - expect(rectangleJson.lineColor).toBe('#fff'); + expect(rectangleJson.lineColor).toBe(getActiveLineColor()); expect(rectangleJson.lineWidth).toBe(1); expect(rectangleJson.type).toBe('Rectangle'); expect(rectangleJson.shapeData.points[0].x).toBe(185); diff --git a/B07_DesignDetail/openwebcad/test/helpers/click.ts b/B07_DesignDetail/openwebcad/test/helpers/click.ts index 88b549b8..31c45275 100644 --- a/B07_DesignDetail/openwebcad/test/helpers/click.ts +++ b/B07_DesignDetail/openwebcad/test/helpers/click.ts @@ -1,4 +1,3 @@ -import { TOOLBAR_WIDTH } from '../../src/App.consts'; import { MouseButton } from '../../src/App.types'; import type { InputController } from '../../src/inputController/input-controller'; @@ -17,7 +16,8 @@ export function click( ) { inputController.handleMouseUp({ button: mouseButton, - clientX: TOOLBAR_WIDTH + x, // Coordinates are relative to the top left of the draw area excluding the toolbar + // 입력 컨트롤러가 캔버스 bounding rect(시험에서는 0)를 빼므로 화면 좌표를 그대로 준다. + clientX: x, clientY: y, preventDefault: () => {}, stopPropagation: () => {}, diff --git a/B07_DesignDetail/openwebcad/test/helpers/tests.consts.ts b/B07_DesignDetail/openwebcad/test/helpers/tests.consts.ts index f3f085c0..1ded2825 100644 --- a/B07_DesignDetail/openwebcad/test/helpers/tests.consts.ts +++ b/B07_DesignDetail/openwebcad/test/helpers/tests.consts.ts @@ -1,4 +1,4 @@ -import { TOOLBAR_WIDTH } from '../../src/App.consts'; - -export const CANVAS_WIDTH = 1920 - TOOLBAR_WIDTH; +// 좌표 변환이 캔버스 bounding rect 를 쓰도록 바뀌어 툴바 폭 상수가 없어졌다 — +// 시험 캔버스는 화면 전체 폭을 그대로 쓴다. +export const CANVAS_WIDTH = 1920; export const CANVAS_HEIGHT = 1080; diff --git a/B07_DesignDetail/openwebcad/test/tools/eraser/eraser.test.ts b/B07_DesignDetail/openwebcad/test/tools/eraser/eraser.test.ts index 3a7c898a..17f10539 100644 --- a/B07_DesignDetail/openwebcad/test/tools/eraser/eraser.test.ts +++ b/B07_DesignDetail/openwebcad/test/tools/eraser/eraser.test.ts @@ -4,7 +4,7 @@ import type { ArcJsonData } from '../../../src/entities/ArcEntity'; import { EntityName, type JsonEntity } from '../../../src/entities/Entity'; import type { LineJsonData } from '../../../src/entities/LineEntity'; import { pointDistance } from '../../../src/helpers/distance-between-points'; -import { getEntities } from '../../../src/state'; +import { getActiveLineColor, getEntities } from '../../../src/state'; import { initApplication } from '../../helpers/init-application'; import { replayRecording } from '../../helpers/replay-recording'; import { CANVAS_HEIGHT } from '../../helpers/tests.consts'; @@ -23,7 +23,7 @@ test('Draw circle and line and erase part of circle', async () => { expect(lineEntity.getType()).toBe(EntityName.Line); const lineJson = (await lineEntity.toJson()) as JsonEntity; - expect(lineJson.lineColor).toBe('#fff'); + expect(lineJson.lineColor).toBe(getActiveLineColor()); expect(lineJson.lineWidth).toBe(1); expect(lineJson.type).toBe('Line'); expect(lineJson.shapeData.startPoint.x).toBe(473); @@ -35,7 +35,7 @@ test('Draw circle and line and erase part of circle', async () => { expect(arcEntity.getType()).toBe(EntityName.Arc); const arcJson = (await arcEntity.toJson()) as JsonEntity; - expect(arcJson.lineColor).toBe('#fff'); + expect(arcJson.lineColor).toBe(getActiveLineColor()); expect(arcJson.lineWidth).toBe(1); expect(arcJson.type).toBe('Arc'); expect(arcJson.shapeData.center.x).toBe(266); @@ -45,6 +45,9 @@ test('Draw circle and line and erase part of circle', async () => { new Point(542, CANVAS_HEIGHT - 441) ); expect(arcJson.shapeData.radius).toBeCloseTo(radius, 5); - expect(arcJson.shapeData.startAngle).toBeCloseTo(0.7338182524767606, 5); - expect(arcJson.shapeData.endAngle).toBeCloseTo(-0.7338182524767606, 5); + // 각은 [0, 2π)로 정규화해 비교한다 — 같은 각을 음수로도 2π 더한 값으로도 쓸 수 있다. + const turn = 2 * Math.PI; + const normalize = (angle: number) => ((angle % turn) + turn) % turn; + expect(normalize(arcJson.shapeData.startAngle)).toBeCloseTo(normalize(0.7338182524767606), 5); + expect(normalize(arcJson.shapeData.endAngle)).toBeCloseTo(normalize(-0.7338182524767606), 5); });