import { describe, expect, it } from 'vitest'; import { buildPier } from '../src/core/geometry'; import { DEFAULT_MATERIAL, materialFor } from '../src/core/rebar/common'; import { rebarSummary, type RebarRow } from '../src/core/rebar/summary'; import { parseProject, SCHEMA_VERSION } from '../src/core/project'; import { loadP1 } from './helpers'; const model = buildPier(loadP1()); const byName = (rows: RebarRow[]) => new Map(rows.map((r) => [r.name, r])); const base = rebarSummary(model).rows; const members = (rows: RebarRow[], member: RebarRow['member']) => rows.filter((r) => r.member === member); /** Rows of one member whose length or count changed against the base. */ const changedIn = (rows: RebarRow[], member: RebarRow['member']) => members(rows, member) .filter((r) => { const b = byName(base).get(r.name)!; return b.length !== r.length || b.count !== r.count; }) .map((r) => r.name); describe('② 부재별 fck (spec §6.2 · ②)', () => { it('a member with its own fck uses it; the others keep the 공통 value', () => { const common = { ...DEFAULT_MATERIAL, fck: 30 }; expect(materialFor(common, { coping: 35 }, 'coping').fck).toBe(35); expect(materialFor(common, { coping: 35 }, 'column').fck).toBe(30); expect(materialFor(common, { coping: 35 }, 'coping').grade).toEqual(common.grade); }); it('a coping fck changes only the coping rows (C15 stair legs use the H19 development length)', () => { const rows = rebarSummary(model, DEFAULT_MATERIAL, { coping: 20 }).rows; expect(changedIn(rows, 'column')).toEqual([]); expect(changedIn(rows, 'footing')).toEqual([]); expect(changedIn(rows, 'coping')).toContain('C15'); expect(byName(rows).get('C15')!.length!).toBeGreaterThan(byName(base).get('C15')!.length!); }); it('a column fck changes only the column rows', () => { const rows = rebarSummary(model, DEFAULT_MATERIAL, { column: 35 }).rows; expect(changedIn(rows, 'coping')).toEqual([]); expect(changedIn(rows, 'footing')).toEqual([]); expect(changedIn(rows, 'column').length).toBeGreaterThan(0); }); it('a footing fck changes only the footing rows (B6 splice, 방향 교환)', () => { const rows = rebarSummary(model, DEFAULT_MATERIAL, { footing: 35 }).rows; expect(changedIn(rows, 'coping')).toEqual([]); expect(changedIn(rows, 'column')).toEqual([]); expect(changedIn(rows, 'footing')).toContain('B6'); }); it('a per-member fck equal to the 공통 value changes nothing', () => { expect(rebarSummary(model, DEFAULT_MATERIAL, { coping: 27, column: 27, footing: 27 }).rows).toEqual(base); }); it('I02 stays while a member uses the assumed fck; it goes when all three members have their own fck', () => { const codes = (byMember: Parameters[2]) => rebarSummary(model, DEFAULT_MATERIAL, byMember).warnings.map((w) => w.code); expect(codes({})).toContain('I02'); expect(codes({ coping: 35, column: 35 })).toContain('I02'); expect(codes({ coping: 35, column: 35, footing: 35 })).not.toContain('I02'); }); }); describe('② 부재별 fck: 프로젝트 파일', () => { const file = (fckByMember: unknown) => JSON.stringify({ schemaVersion: SCHEMA_VERSION, project: {}, source: {}, piers: [], history: [], fckByMember }); it('accepts known members with positive numbers, and an absent field', () => { expect(parseProject(file({ coping: 35, column: 30 })).fckByMember).toEqual({ coping: 35, column: 30 }); expect(parseProject(file(undefined)).fckByMember).toBeUndefined(); }); it('refuses an unknown member or a value that is not a positive number', () => { expect(() => parseProject(file({ bearing: 35 }))).toThrow('부재별 fck'); expect(() => parseProject(file({ coping: 0 }))).toThrow('부재별 fck'); expect(() => parseProject(file({ coping: '35' }))).toThrow('부재별 fck'); }); });