import { describe, expect, it } from 'vitest'; import { buildPier } from '../src/core/geometry'; import { validateInput, validatePier } from '../src/core/validate'; import type { Issue, PierInput } from '../src/core/types'; import { loadP1 } from './helpers'; function run(input: PierInput): Issue[] { const issues: Issue[] = []; if (validateInput(input, issues)) validatePier(input, buildPier(input), issues); return issues; } describe('validation (spec §10)', () => { it('P1 raises I01 (block 100 mm < cover + bar 119 mm) and no E/W', () => { const issues = run(loadP1()); expect(issues.map((i) => i.code)).toContain('I01'); expect(issues.filter((i) => i.level !== 'I')).toEqual([]); }); it('E01 when the pier type and column count disagree', () => { const issues = run({ ...loadP1(), type: '2주형' }); expect(issues.map((i) => i.code)).toContain('E01'); }); it('E02 when front and back bearing counts differ', () => { const p1 = loadP1(); const issues: Issue[] = []; const bad = { ...p1, bearings: p1.bearings.filter((b) => !(b.row === '후열' && b.no === 10)) }; expect(validateInput(bad, issues)).toBe(false); expect(issues.map((i) => i.code)).toContain('E02'); }); it('W01 when IP2 disagrees with the 1:9 recalculation by more than 1 mm', () => { const p1 = loadP1(); const ips = p1.ips.map((p) => (p.name === 'IP2' ? { ...p, x: p.x + 0.002 } : p)); expect(run({ ...p1, ips }).map((i) => i.code)).toContain('W01'); }); }); describe('foundation types', () => { it('직접기초: footing, no piles', () => { const m = buildPier({ ...loadP1(), foundationType: '직접기초' }); expect(m.footings).toHaveLength(1); expect(m.piles).toHaveLength(0); }); it('직접기초: blinding concrete under the footing (Q4 추천안, 적용)', () => { const m = buildPier({ ...loadP1(), foundationType: '직접기초' }); expect(m.blindings).toHaveLength(1); expect(m.blindings[0].thickness).toBeCloseTo(0.1, 6); }); it('말뚝기초: steel pipe pile 609.6 mm (Q5 추천안)', () => { const m = buildPier(loadP1()); expect(m.piles.length).toBeGreaterThan(0); expect(m.piles[0].diameter).toBeCloseTo(0.6096, 6); }); it('2주형: underside between columns drops 300 below Level, T형 has no such point (Q3 추천안)', () => { const t = buildPier(loadP1()); const dip = t.levelBottom - 0.3; expect(t.profile.some(([, z]) => Math.abs(z - dip) < 1e-9)).toBe(false); const two = buildPier({ ...loadP1(), type: '2주형', columnCount: 2 }); expect(two.profile.some(([, z]) => Math.abs(z - dip) < 1e-9)).toBe(true); const outlineLow = Math.min(...two.profile.map(([, z]) => z)); expect(outlineLow).toBeCloseTo(dip, 9); }); it('3주형 builds with two gaps of haunches', () => { const m = buildPier({ ...loadP1(), type: '3주형', columnCount: 3 }); const dips = m.profile.filter(([, z]) => Math.abs(z - (m.levelBottom - 0.3)) < 1e-9); expect(dips).toHaveLength(4); }); it('현장타설말뚝: no footing, one pile of column diameter per column, tip = bottom EL − length', () => { const m = buildPier({ ...loadP1(), foundationType: '현장타설말뚝' }); expect(m.footings).toHaveLength(0); expect(m.piles).toHaveLength(1); expect(m.piles[0].diameter).toBeCloseTo(2.0, 6); expect(m.piles[0].bottom).toBeCloseTo(26.208 - 24, 6); }); }); describe('bearing and block checks (spec §10 W03, W04)', () => { it('P1 has no W03 or W04', () => { const codes = run(loadP1()).map((i) => i.code); expect(codes).not.toContain('W03'); expect(codes).not.toContain('W04'); }); it('W03 when a bearing centre is closer than half a block width to a step boundary', () => { const m = buildPier(loadP1()); const boundary = m.segments[1].s0; const bearings = m.bearings.map((b, k) => (k === 0 ? { ...b, s: boundary + 0.3 } : b)); const issues: Issue[] = []; validatePier(loadP1(), { ...m, bearings }, issues); expect(issues.map((i) => i.code)).toContain('W03'); }); it('W04 when a block footprint extends past the coping end', () => { const m = buildPier(loadP1()); const blocks = m.blocks.map((b, k) => (k === 0 ? { ...b, s: 0.1 } : b)); const issues: Issue[] = []; validatePier(loadP1(), { ...m, blocks }, issues); expect(issues.map((i) => i.code)).toContain('W04'); }); });