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); }); });