import { describe, expect, it } from 'vitest'; import { buildPier, type BlockModel } from '../src/core/geometry'; import { buildIntegratedBlocks, INTEGRATED_BLOCK } from '../src/core/integrated-block'; import { bearingBlockRebar, integratedBlockBars, totals } from '../src/core/rebar/bearing-block'; import { loadP1 } from './helpers'; const input = { ...loadP1(), bearingLayout: '일체' as const }; const model = buildPier(input); describe('일체 블록 모델 (승인 2026-10-26)', () => { it('P1 with 일체: one stepped block per pair (5), 2500 along the 교축 and 1200 along the 교각축', () => { expect(model.integratedBlocks).toHaveLength(5); expect(INTEGRATED_BLOCK).toEqual({ length: 2500, depth: 1200 }); const first = model.integratedBlocks[0]; expect(first.frontNo).toBe(1); expect(first.backNo).toBe(6); }); it('the block is centred on the pair midpoint, and its two halves keep their own tops (단이 있는 블록)', () => { const ib = model.integratedBlocks[0]; const f = model.blocks.find((b) => b.no === 1)!; const b = model.blocks.find((x) => x.no === 6)!; expect(ib.n).toBeCloseTo((f.n + b.n) / 2, 9); expect(ib.s).toBeCloseTo((f.s + b.s) / 2, 9); expect(ib.topFront).toBe(f.top); expect(ib.topBack).toBe(b.top); // the front and back tops differ on P1 (150 vs 200 mm), so the block is stepped expect(Math.abs(ib.topFront - ib.topBack)).toBeGreaterThan(0.01); }); it('the 2500 block covers the pair: the front and back bearings sit 600 mm inside each end (C1 600)', () => { const ib = model.integratedBlocks[0]; const f = model.blocks.find((b) => b.no === 1)!; const b = model.blocks.find((x) => x.no === 6)!; const half = INTEGRATED_BLOCK.length / 2000; // x = 북 convention (2026-11-03): the front row is on the high-n side, so the block spans min..max of the pair. expect(ib.n - half).toBeCloseTo(Math.min(f.n, b.n) - 0.6, 2); expect(ib.n + half).toBeCloseTo(Math.max(f.n, b.n) + 0.6, 2); }); it('분리 keeps no integrated blocks', () => { expect(buildPier(loadP1()).integratedBlocks).toEqual([]); }); it('a staggered pair blocks 일체: no integrated blocks, the model stays 분리', () => { const blocks = [{ no: 1, row: '전열', s: 1, n: -0.7, top: 0.5, bottom: 0, rowSkewDeg: 0 }, { no: 1, row: '후열', s: 1.2, n: 0.6, top: 0.5, bottom: 0, rowSkewDeg: 0 }] as unknown as BlockModel[]; expect(buildIntegratedBlocks(blocks, true)).toEqual([]); }); }); describe('일체 블록 철근 (승인 2026-10-26, 받침블럭철근.md 일체 a 2500)', () => { it('S2: 19 bars across the 2500 length, 10 on the front half and 9 on the back half (the centre bar on the front)', () => { const rows = integratedBlockBars(model.integratedBlocks[0]).filter((r) => r.name === 'S2'); expect(rows.reduce((s, r) => s + r.count, 0)).toBe(19); expect(rows.map((r) => r.count)).toEqual([10, 9]); expect(rows[0].length).toBe(2500); }); it('S1: 8 bars across the 1200 depth, 3800 long (2500 − 100 + 2 × 700)', () => { const s1 = integratedBlockBars(model.integratedBlocks[0]).find((r) => r.name === 'S1')!; expect(s1.count).toBe(8); expect(s1.length).toBe(3800); }); it('a pier of five integrated blocks has S1 40 and S2 95 (per-bearing 분리 gives 80 and 80)', () => { const t = totals(bearingBlockRebar(model)); expect(t).toEqual({ S1: 40, S2: 95 }); const sep = totals(bearingBlockRebar(buildPier(loadP1()))); expect(sep).toEqual({ S1: 80, S2: 80 }); }); }); describe('일체 블록 3D 철근과 장면', () => { it('the 3D rebar and the scene build for a 일체 pier, with the S2 bars counted per pair (19 each)', async () => { const { buildRebar3D } = await import('../src/view/rebar3d'); const { buildPierScene } = await import('../src/view/scene'); const rb = buildRebar3D(model); expect(rb.group.children.length).toBeGreaterThan(0); const scene = buildPierScene(model, undefined); // the blocks group holds the stepped halves (2 per pair) and no per-bearing blocks expect(scene.parts.blocks.children.length).toBeGreaterThan(0); expect(scene.parts.blocks.children.length).toBe(model.integratedBlocks.length * 2); }); });