import { describe, expect, it } from 'vitest'; import { groundElFor, parseGroundModel } from '../src/core/ground-model'; import { changedKeys, overrideKeys, revertDraft, type PierValues } from '../src/core/changes'; import { parseProject, serializeProject, SCHEMA_VERSION, type ProjectFile } from '../src/core/project'; import { loadP1 } from './helpers'; /** The shape written by RAG/pierzainer_ground (schema 1). */ const SAMPLE = JSON.stringify({ schema: 1, generated: '2026-10-23', coord: '측량 X(북) · Y(동), m. 치수 mm', piers: [ { id: 'P1', ground: { center: 29.355, excel: 28.931, used: 29.355, source: '계산' }, footing: { cover: 0.972 } }, { id: 'P2', ground: { center: null, excel: null, used: null, source: '없음' } }, ], }); describe('지반 모델 읽기 (ground_model.json, schema 1)', () => { it('reads the pier-centre ground EL and the excel value', () => { const m = parseGroundModel(SAMPLE); expect(m.piers[0]).toEqual({ id: 'P1', centerEl: 29.355, excelEl: 28.931 }); expect(groundElFor(m, 'P1')).toBe(29.355); }); it('a pier outside the terrain has no value, and an unknown pier has none either', () => { const m = parseGroundModel(SAMPLE); expect(groundElFor(m, 'P2')).toBeUndefined(); expect(groundElFor(m, 'P9')).toBeUndefined(); }); it('refuses another schema number instead of guessing the layout', () => { expect(() => parseGroundModel(SAMPLE.replace('"schema":1', '"schema":2'))).toThrow('형식 번호 2'); }); it('refuses a file without a pier list or without pier names', () => { expect(() => parseGroundModel('{"schema": 1}')).toThrow('교각 목록'); expect(() => parseGroundModel('{"schema": 1, "piers": [{"ground": {}}]}')).toThrow('이름(id)'); }); it('refuses text that is not JSON', () => { expect(() => parseGroundModel('not json')).toThrow('JSON으로 읽히지 않습니다'); }); }); describe('지반고 적용 (사용자가 누를 때만, 변경으로 기록)', () => { const base = loadP1(); const pier: PierValues = { defaults: base, input: base, draft: { ...base, groundEl: 29.355 } }; it('an applied value counts as an override and can be reverted to the excel value', () => { expect(overrideKeys(pier)).toContain('groundEl'); expect(revertDraft(pier, 'groundEl').groundEl).toBe(base.groundEl); }); it('the draft and the generated input are compared, so the change waits for regeneration', () => { expect(changedKeys(pier.input, pier.draft)).toEqual(['groundEl']); }); }); describe('프로젝트 파일의 지반 모델', () => { function project(extra: Partial): ProjectFile { const p1 = loadP1(); return { schemaVersion: SCHEMA_VERSION, project: { name: 'x', bridge: '운곡교', client: '', author: '', crs: 'KATEC', memo: '' }, source: { fileName: 'x.xlsx', sheets: [] }, piers: [{ id: 'P1', input: p1 }], history: [], ...extra, }; } it('keeps the imported ground model across save and open', () => { const m = parseGroundModel(SAMPLE); const reopened = parseProject(serializeProject(project({ groundModel: m }))); expect(reopened.groundModel).toEqual(m); }); it('refuses a damaged ground model inside the project file', () => { const text = serializeProject(project({})).replace('"history": []', '"history": [], "groundModel": {"schema": 1}'); expect(() => parseProject(text)).toThrow('교각 목록'); }); it('opens an older file that has no ground model', () => { expect(parseProject(serializeProject(project({}))).groundModel).toBeUndefined(); }); });