- 112-1 면적 표 변환을 공용 파일(spreadsheet_area_table.ts)로 빼 서버 Node 와 브라우저가 한 벌로 씀 · 도면 패널이 칸 고침마다(값 바뀐 표만 · 펼친 동안만) CAD 연결 표 갈기를 보기 전용 갈음(preview)으로 보냄 - 112-2 보낸 범위 이름은 도면 [저장] 요청 안에서 서버가 면적 문서에 둠(놓기만 하고 저장 안 하면 안 생김) · [보내기 취소]는 화면에서만 빼고 면적 [저장] 때 서버가 연결표 줄 · 이름을 뺌 · /name · /unlink 길 삭제 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
192 lines
6.7 KiB
TypeScript
192 lines
6.7 KiB
TypeScript
import { Point } from '@flatten-js/core';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { Actor } from 'xstate';
|
|
import { TableEntity } from '../entities/TableEntity.ts';
|
|
import { ActorEvent } from '../tools/tool.types.ts';
|
|
import {
|
|
getEntities,
|
|
isDrawingDirty,
|
|
setEntities,
|
|
setHostReadOnly,
|
|
setLayers,
|
|
} from '../state.ts';
|
|
import {
|
|
getPendingTable,
|
|
placeTableToolStateMachine,
|
|
setPendingTable,
|
|
tableAt,
|
|
} from '../tools/annotate/place-table-tool.ts';
|
|
import {
|
|
AISLO_PLACE_CANCELLED_MESSAGE,
|
|
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('놓지 않고 Esc = place-cancelled 를 한 번 부모에 알리고 맡긴 표를 비움', async () => {
|
|
const notify = vi.fn();
|
|
await handleTableMessage(
|
|
{ type: AISLO_PLACE_TABLE_MESSAGE, entity: await table().toJson(), link: 'T3' },
|
|
notify
|
|
);
|
|
const actor = new Actor(placeTableToolStateMachine);
|
|
actor.start();
|
|
actor.send({ type: ActorEvent.ESC });
|
|
actor.send({ type: ActorEvent.ESC });
|
|
expect(notify).toHaveBeenCalledTimes(1);
|
|
expect(notify).toHaveBeenCalledWith(AISLO_PLACE_CANCELLED_MESSAGE, {});
|
|
expect(getPendingTable()).toBeNull();
|
|
});
|
|
|
|
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('보기 전용 도면은 막고(0) · preview 로 온 갈기만 보기 전용에서도 갈음(112-1)', async () => {
|
|
const linked = table('T1');
|
|
setEntities([linked]);
|
|
setHostReadOnly(true);
|
|
const cells = [
|
|
[{ text: '항목' }, null],
|
|
[{ text: '면적' }, { text: '77.0' }],
|
|
];
|
|
const notify = vi.fn();
|
|
await handleTableMessage({ type: AISLO_UPDATE_TABLE_MESSAGE, link: 'T1', cells }, notify);
|
|
expect(notify).toHaveBeenLastCalledWith(AISLO_TABLE_UPDATED_MESSAGE, { link: 'T1', count: 0 });
|
|
expect(linked.getCell(1, 1)?.text).toBe('12.5');
|
|
await handleTableMessage(
|
|
{ type: AISLO_UPDATE_TABLE_MESSAGE, link: 'T1', cells, preview: true },
|
|
notify
|
|
);
|
|
expect(notify).toHaveBeenLastCalledWith(AISLO_TABLE_UPDATED_MESSAGE, { link: 'T1', count: 1 });
|
|
expect(linked.getCell(1, 1)?.text).toBe('77.0');
|
|
setHostReadOnly(false);
|
|
});
|
|
|
|
it('표 메시지가 아니면 false', async () => {
|
|
expect(await handleTableMessage({ type: 'aislo:b08:save-request' }, vi.fn())).toBe(false);
|
|
});
|
|
});
|