import { describe, expect, it } from 'vitest'; import { buildPier } from '../src/core/geometry'; import { buildRebar3D } from '../src/view/rebar3d'; import { chainDims, copingBarGroup, copingFrontSvg, copingSectionSvg } from '../src/view/coping-rebar-svg'; import { loadP1 } from './helpers'; const model = buildPier(loadP1()); const scene = buildRebar3D(model); describe('코핑 배근 정면 · 단면 (승인 2026-10-26)', () => { it('the 3D bar records include the coping bars, the same data as the meshes', () => { const coping = scene.records.filter((r) => /^C\d/.test(r.name)); expect(coping.length).toBeGreaterThan(0); expect(scene.records.length).toBeGreaterThan(coping.length - 1); }); it('the front view draws one polyline per coping bar', () => { const coping = scene.records.filter((r) => /^C\d/.test(r.name)); const svg = copingFrontSvg(scene.records, model.copingL * 1000); expect((svg.match(/ { expect(copingBarGroup('C1')).toBe('main'); expect(copingBarGroup('C9')).toBe('loop'); expect(copingBarGroup('C12')).toBe('side'); expect(copingBarGroup('C15')).toBe('step'); }); it('the section at mid-length shows the bars that cross it', () => { const svg = copingSectionSvg(scene.records, model.copingL / 2); expect((svg.match(/ { it('writes each bar name once, and each spot once', () => { const svg = copingSectionSvg(scene.records, 6); const labels = (svg.match(/]*>(C\d+) s.replace(/.*>/, ''))); expect(labels).toBe(names.size); }); }); describe('정면 번호 풍선과 치수 (승인 2026-10-26, 시안 ③)', () => { const svg = copingFrontSvg(scene.records, model.copingL * 1000); it('has one bubble per bar group, with its label', () => { for (const label of ['C1–6', 'C12·13', 'C9', 'C10', 'C11', 'C7·8', 'C14', 'C15', 'C16']) { expect(svg).toContain(`>${label}<`); } }); it('writes the 시안 length dimension (300 · 4500 · 2400 · 4500 · 300 = 12000)', () => { expect(svg).toContain('18@250=4500'); expect(svg).toContain('8@300=2400'); expect(svg).toContain('코핑 길이 12000'); }); }); describe('정면 두 번째 치수선과 범례 (승인 2026-10-26, 시안)', () => { const svg = copingFrontSvg(scene.records, model.copingL * 1000); it('writes the column-centre span 10@1000=10000 and the legend', () => { expect(svg).toContain('10@1000=10000'); expect(svg).toContain('기둥 중심'); expect(svg).toContain('주철근 (C1–C8)'); expect(svg).toContain('전단·띠 (C9–C11)'); }); }); describe('정면 치수는 3D 철근 위치에서 계산 (승인 2026-10-26)', () => { it('the chains come from the bar positions and match the 시안 values', () => { const svg = copingFrontSvg(scene.records, model.copingL * 1000, model.columns[0].s); const texts = [...svg.matchAll(/font-size="150"[^>]*>([^<]*) x[1]); expect(texts).toEqual(['300', '18@250=4500', '8@300=2400', '18@250=4500', '300', '900', '10@1000=10000', '900', '기둥 중심']); }); }); describe('chainDims', () => { it('groups repeated gaps and writes a single gap as its length', () => { expect(chainDims([300, 550, 800, 1100], 0, 1400).map((d) => d.text)).toEqual(['300', '2@250=500', '2@300=600']); }); });