Files
Aislo/B07_DesignDetail/openwebcad/src/integration/aislo-table-bridge.test.ts
T
eomsangdonandClaude Opus 5.5 b1dbd9c3cb fix(B07 CAD): 서버가 만든 표 선 색 · 굵기 · id 비면 기본값 — 놓기 · 업데이트 · 다시 열기 한 곳 (43-6)
- TableEntity.fromJson: lineColor 비면 자동색 #fff · lineWidth 비면 1 · id 비면 새 id(면적산출 → 도면 표 변환 답에 세 칸이 없음 → 저장 JSON 선 색 빠지던 흠 E2)
- 시험 aislo-table-bridge.test.ts 에 선 색 없는 표 놓기 · 저장 JSON 선 색 · 다시 읽기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
2026-10-05 00:26:53 +09:00

142 lines
5.1 KiB
TypeScript

import { Point } from '@flatten-js/core';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { TableEntity } from '../entities/TableEntity.ts';
import { getEntities, isDrawingDirty, setEntities, setLayers } from '../state.ts';
import { getPendingTable, setPendingTable, tableAt } from '../tools/annotate/place-table-tool.ts';
import {
AISLO_PLACE_TABLE_MESSAGE,
AISLO_TABLE_UPDATED_MESSAGE,
AISLO_UPDATE_TABLE_MESSAGE,
handleTableMessage,
updateLinkedTables,
} from './aislo-table-bridge.ts';
const run = vi.hoisted(() => vi.fn());
vi.mock('../commands/run-command.ts', () => ({ runCommandInput: run }));
const layer = { id: 'layer-1', isLocked: false, isVisible: true, name: '작업' };
function table(link?: string): TableEntity {
return new TableEntity(
layer.id,
new Point(10, 20),
[30, 15],
[5, 5],
[
[{ text: '항목', colSpan: 2, key: 'S1!A1' }, null],
[
{ text: '면적', key: 'S1!A2' },
{ text: '12.5', align: 'right', key: 'S1!B2' },
],
],
{ fontSize: 2.5, ...(link ? { 연결: link } : {}) }
);
}
beforeEach(() => {
setLayers([layer]);
setEntities([]);
setPendingTable(null);
run.mockClear();
});
describe('표 놓기(PLACETABLE)', () => {
it('미리보기 · 놓을 표는 커서 자리(왼쪽 위)에 같은 칸 · 크기 · 연결로', () => {
const placed = tableAt(table('T1'), new Point(100, 200), layer.id);
expect([placed.getOrigin().x, placed.getOrigin().y]).toEqual([100, 200]);
expect(placed.getColumnWidths()).toEqual([30, 15]);
expect(placed.getCell(1, 1)?.text).toBe('12.5');
expect(placed.getStyle().연결).toBe('T1');
});
it('place-table 메시지 = 연결을 단 표를 맡기고 PLACETABLE 을 켬', async () => {
const notify = vi.fn();
const handled = await handleTableMessage(
{
type: AISLO_PLACE_TABLE_MESSAGE,
entity: { ...(await table().toJson()), lineColor: '#123456' },
link: 'T7',
},
notify
);
expect(handled).toBe(true);
expect(run).toHaveBeenCalledWith('PLACETABLE');
expect(getPendingTable()?.getStyle().연결).toBe('T7');
expect(getPendingTable()?.getCell(0, 0)?.colSpan).toBe(2);
expect(getPendingTable()?.lineColor).toBe('#123456');
expect(notify).not.toHaveBeenCalled();
});
it('옮기기 · 복사(clone) · JSON 왕복에도 style.연결이 남음', async () => {
const linked = table('T1');
expect(linked.clone().getStyle().연결).toBe('T1');
const json = await linked.toJson();
if (!json) throw new Error('표 JSON 없음');
const back = await TableEntity.fromJson(json);
expect(back.getStyle().연결).toBe('T1');
});
});
describe('선 색 없는 표(서버 표 변환 답 · 43-6)', () => {
it('id · 선 색 · 굵기가 없으면 기본값(자동색 #fff · 1 · 새 id)으로 채워 놓고 저장 JSON 에도 남음', async () => {
const json = await table('T8').toJson();
if (!json) throw new Error('표 JSON 없음');
const { id: _id, lineColor: _color, lineWidth: _width, layerId: _layer, ...bare } = json;
await handleTableMessage(
{ type: AISLO_PLACE_TABLE_MESSAGE, entity: bare as typeof json, link: 'T8' },
vi.fn()
);
const pending = getPendingTable();
expect(pending?.lineColor).toBe('#fff');
expect(pending?.lineWidth).toBe(1);
const placed = tableAt(pending as TableEntity, new Point(0, 0), layer.id);
placed.lineColor = pending?.lineColor ?? '';
const saved = await placed.toJson();
expect(saved?.lineColor).toBe('#fff');
const back = await TableEntity.fromJson(bare as typeof json);
expect(back.id).toBeTruthy();
expect(back.lineColor).toBe('#fff');
});
});
describe('표 칸 갈기(update-table)', () => {
it('연결이 같은 표의 칸만 갈고 자리 · 크기는 그대로 · 되돌리기 한 걸음', () => {
const linked = table('T1');
const other = table('T2');
setEntities([linked, other]);
const count = updateLinkedTables('T1', [
[{ text: '항목(새)', colSpan: 2 }, null],
[{ text: '면적' }, { text: '99.0', align: 'right' }],
]);
expect(count).toBe(1);
expect(linked.getCell(1, 1)?.text).toBe('99.0');
expect(linked.getCell(0, 1)).toBeNull(); // 병합 그대로
expect(other.getCell(1, 1)?.text).toBe('12.5');
expect([linked.getOrigin().x, linked.getColumnWidths()]).toEqual([10, [30, 15]]);
expect(isDrawingDirty()).toBe(true);
});
it('넘치는 칸은 버리고 모자란 칸은 빈 칸', () => {
const linked = table('T1');
setEntities([linked]);
updateLinkedTables('T1', [[{ text: 'a' }, { text: 'b' }, { text: '넘침' }]]);
expect(linked.getCells().map((row) => row.map((cell) => cell?.text ?? null))).toEqual([
['a', 'b'],
['', ''],
]);
});
it('update-table 메시지 = table-updated{link, count} · 없는 연결은 0 · 도면 그대로', async () => {
setEntities([table('T1')]);
const before = getEntities();
const notify = vi.fn();
await handleTableMessage({ type: AISLO_UPDATE_TABLE_MESSAGE, link: 'T9', cells: [] }, notify);
expect(notify).toHaveBeenCalledWith(AISLO_TABLE_UPDATED_MESSAGE, { link: 'T9', count: 0 });
expect(getEntities()).toBe(before);
});
it('표 메시지가 아니면 false', async () => {
expect(await handleTableMessage({ type: 'aislo:b08:save-request' }, vi.fn())).toBe(false);
});
});