import { describe, expect, it } from 'vitest'; import { buildPier } from '../src/core/geometry'; import { assemblyJs, footingRuleValues, gridPositions } from '../src/core/rebar/footing'; import { DEFAULT_FOOTING_REBAR, FOOTING_REBAR_RANGES, footingSettingsIssues } from '../src/core/rebar/footing-settings'; import { rebarSummary } from '../src/core/rebar/summary'; import { loadP1 } from './helpers'; describe('확대기초 배근 입력 (승인 2026-10-26, 시안 ③ 확대기초)', () => { it('the defaults are inside every range and raise no issue', () => { expect(footingSettingsIssues(DEFAULT_FOOTING_REBAR)).toEqual([]); for (const [key, [lo, hi]] of Object.entries(FOOTING_REBAR_RANGES)) { const v = DEFAULT_FOOTING_REBAR[key as keyof typeof DEFAULT_FOOTING_REBAR]; expect(v >= lo && v <= hi, key).toBe(true); } }); it('a value outside its range gives FT01; a non-integer n gives FT02', () => { expect(footingSettingsIssues({ ...DEFAULT_FOOTING_REBAR, cTop: 151 })).toEqual([expect.objectContaining({ code: 'FT01', key: 'cTop' })]); expect(footingSettingsIssues({ ...DEFAULT_FOOTING_REBAR, n: 2.5 })).toEqual([expect.objectContaining({ code: 'FT02', key: 'n' })]); }); it('the grid spacing s reaches the grid positions and the rebar table', () => { const m = buildPier({ ...loadP1(), footingRebar: { sL: 150 } }); expect(m.footingRebar.sL).toBe(150); const xs = gridPositions(8000, m.footingRebar); expect(xs[1] - xs[0]).toBeCloseTo(150, 6); const b4 = rebarSummary(m).rows.find((r) => r.name === 'B4')!; const b4Default = rebarSummary(buildPier(loadP1())).rows.find((r) => r.name === 'B4')!; expect(b4.count).not.toBe(b4Default.count); }); it('철근표 간격: P1 기본값 B2 250 / 125 · B10-1 200 (기본값); s 150 → B2 300 / 150 · B1 300 (입력)', () => { const base = rebarSummary(buildPier(loadP1())).rows; const b2 = base.find((r) => r.name === 'B2')!; expect([b2.spacing, b2.actual, b2.basis]).toEqual(['250', '125', 'default']); const b10 = base.find((r) => r.name === 'B10-1')!; expect([b10.spacing, b10.basis]).toEqual(['200', 'default']); const rows = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sL: 150 } })).rows; const b2s = rows.find((r) => r.name === 'B2')!; expect([b2s.spacing, b2s.actual, b2s.basis]).toEqual(['300', '150', 'input']); const b1s = rows.find((r) => r.name === 'B1')!; expect([b1s.spacing, b1s.actual]).toEqual(['300', '300']); expect(rows.find((r) => r.name === 'B10-1')!.basis).toBe('default'); }); // 방향 교환 (승인 2026-10-26): sL = spacing along L (B1–B4, B9 columns, B11); sW = spacing along W (B5–B8, B9 rows, B12). it('방향 (교환 후): sW는 B5–B8·B12, sL은 B1–B4·B9·B11', () => { const rows = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sW: 150 } })).rows; expect(rows.find((r) => r.name === 'B5')!.spacing).toBe('300'); expect(rows.find((r) => r.name === 'B5')!.basis).toBe('input'); expect(rows.find((r) => r.name === 'B12')!.spacing).toBe('1200 (8s)'); expect(rows.find((r) => r.name === 'B11')!.basis).toBe('default'); expect(rows.find((r) => r.name === 'B1')!.basis).toBe('default'); const rowsL = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sL: 150 } })).rows; expect(rowsL.find((r) => r.name === 'B1')!.spacing).toBe('300'); expect(rowsL.find((r) => r.name === 'B9')!.spacing).toBe('600 (4s)'); expect(rowsL.find((r) => r.name === 'B11')!.spacing).toBe('1200 (8s)'); expect(rowsL.find((r) => r.name === 'B5')!.basis).toBe('default'); }); it('k 배수: k 4 → 간격 500, 개수 60 / 47 (문서 4s 값)', () => { const rows = rebarSummary(buildPier({ ...loadP1(), footingRebar: { kB11: 4, kB12: 4 } })).rows; expect(rows.find((r) => r.name === 'B11')!.count).toBe(60); expect(rows.find((r) => r.name === 'B12')!.count).toBe(47); }); it('the 조립 단 배수 n reaches the assembly levels', () => { const H = 2000; const base = assemblyJs(H, DEFAULT_FOOTING_REBAR); const wider = assemblyJs(H, { ...DEFAULT_FOOTING_REBAR, n: 3 }); expect(wider.length).toBeLessThan(base.length); expect(wider.every((j) => j % 3 === 0)).toBe(true); }); });