126 lines
6.2 KiB
TypeScript
126 lines
6.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import { developmentLength, hookLength, NOMINAL_DIAMETER, spliceLength } from '../src/core/rebar/common';
|
||
import { bottomPiece, topBarLength, topRowEl, topSplicePieces, widthLayout } from '../src/core/rebar/coping-main';
|
||
import { buildPier } from '../src/core/geometry';
|
||
import { rebarSummary } from '../src/core/rebar/summary';
|
||
import { DEFAULT_COPING_REBAR } from '../src/core/rebar/coping-settings';
|
||
import { loadP1 } from './helpers';
|
||
|
||
describe('C1–C8 상·하면 주철근 (코핑철근.md §5, plan C3)', () => {
|
||
it('nominal diameters: H29 28.6, S32 31.8 (documents use these, not the round numbers)', () => {
|
||
expect(NOMINAL_DIAMETER.H29).toBe(28.6);
|
||
expect(NOMINAL_DIAMETER.S32).toBe(31.8);
|
||
});
|
||
|
||
it('width layout for B 3100: edge 127, ends 110.5, 21@125, 24 bars (12 + 12)', () => {
|
||
const w = widthLayout(3100);
|
||
expect(w.yE).toBe(127);
|
||
expect(w.S).toBe(2846);
|
||
expect(w.n).toBe(21);
|
||
expect(w.g).toBe(110.5);
|
||
expect(w.count).toBe(24);
|
||
expect(w.cyclePerKind).toBe(12);
|
||
expect(w.text).toBe('127 · 110.5 · 21@125 · 110.5 · 127');
|
||
});
|
||
|
||
it('주철근 기본간격 input drives the layout: 140 → 19@140 (22 bars, even), 120 → 22@120 (25, odd)', () => {
|
||
const w140 = widthLayout(3100, { ...DEFAULT_COPING_REBAR, mainSpacing: 140 });
|
||
expect(w140.spacing).toBe(140);
|
||
expect(w140.n).toBe(19);
|
||
expect(w140.count).toBe(22);
|
||
expect(w140.text).toBe(`${w140.yE} · ${w140.g} · 19@140 · ${w140.g} · ${w140.yE}`);
|
||
const w120 = widthLayout(3100, { ...DEFAULT_COPING_REBAR, mainSpacing: 120 });
|
||
expect(w120.n).toBe(22);
|
||
expect(w120.count).toBe(25);
|
||
});
|
||
|
||
it('철근표 간격: P1 기본값은 250 / 125 · 기본값 근거, 기본간격 140이면 280 / 140 · 입력 근거', () => {
|
||
const base = rebarSummary(buildPier(loadP1())).rows;
|
||
const c1 = base.find((r) => r.name === 'C1')!;
|
||
expect([c1.spacing, c1.actual, c1.basis]).toEqual(['250', '125', 'default']);
|
||
const c12 = base.find((r) => r.name === 'C12')!;
|
||
expect(c12.basis).toBe('default');
|
||
const c15 = base.find((r) => r.name === 'C15')!;
|
||
expect([c15.spacing, c15.actual, c15.basis]).toEqual(['250', '250', 'default']);
|
||
|
||
const m = buildPier({ ...loadP1(), copingRebar: { mainSpacing: 140 }, blockRebar: { spacingS1: 130 } });
|
||
const rows = rebarSummary(m).rows;
|
||
const c8 = rows.find((r) => r.name === 'C8')!;
|
||
expect([c8.spacing, c8.actual, c8.basis]).toEqual(['280', '140', 'input']);
|
||
expect(rows.find((r) => r.name === 'C12')!.basis).toBe('default');
|
||
const c16 = rows.find((r) => r.name === 'C16')!;
|
||
expect([c16.spacing, c16.actual, c16.basis]).toEqual(['280', '280', 'input']);
|
||
const s1 = rows.find((r) => r.name === 'S1')!;
|
||
expect([s1.spacing, s1.actual, s1.basis]).toEqual(['130', '130', 'input']);
|
||
});
|
||
|
||
it('상면 이음 (승인 2026-10-26): 기둥 중심 → total/2 + l_s/2 each; 단부 → total + l_s − 12000 and 12000', () => {
|
||
const center = topSplicePieces(12000, 1, 'center')!;
|
||
expect(center.lap).toBeGreaterThan(1700);
|
||
expect(center.pieces[0]).toBeCloseTo(12744 / 2 + center.lap / 2, 3);
|
||
expect(center.pieces[1]).toBeCloseTo(center.pieces[0], 3);
|
||
expect(center.pieces[0] + center.pieces[1]).toBeCloseTo(12744 + center.lap, 3);
|
||
|
||
const end = topSplicePieces(12000, 1, 'end')!;
|
||
expect(end.pieces[0]).toBeCloseTo(12744 + end.lap - 12000, 3);
|
||
expect(end.pieces[1]).toBeCloseTo(12000, 3);
|
||
});
|
||
|
||
it('상면 이음: a bar within the standard length is not split', () => {
|
||
expect(topSplicePieces(10000, 1, 'center')).toBeNull();
|
||
});
|
||
|
||
it('철근표: 이음 무시 → 단일 길이 12744 · 반영(기둥 중심) → 2조각, 각 조각 개수 = 행 개수', () => {
|
||
const ignore = rebarSummary(buildPier(loadP1())).rows.find((r) => r.name === 'C1')!;
|
||
expect(ignore.length).toBeCloseTo(12744, 0);
|
||
expect(ignore.pieces).toBeUndefined();
|
||
|
||
const applied = rebarSummary(buildPier({ ...loadP1(), topSplice: { mode: 'apply', at: 'center' } })).rows;
|
||
const c1 = applied.find((r) => r.name === 'C1')!;
|
||
const lap = topSplicePieces(12000, 1, 'center')!.lap;
|
||
expect(c1.length).toBeNull();
|
||
expect(c1.pieces).toHaveLength(2);
|
||
expect(c1.pieces![0].count).toBe(c1.count);
|
||
expect(c1.pieces![0].length).toBeCloseTo(12744 / 2 + lap / 2, 3);
|
||
expect(c1.pieces![1].length).toBeCloseTo(12744 / 2 + lap / 2, 3);
|
||
expect(c1.note).toContain('기둥 중심');
|
||
|
||
const end = rebarSummary(buildPier({ ...loadP1(), topSplice: { mode: 'apply', at: 'end' } })).rows.find((r) => r.name === 'C1')!;
|
||
expect(end.pieces![1].length).toBeCloseTo(12000, 3);
|
||
expect(end.note).toContain('단부');
|
||
});
|
||
|
||
it('CP04: the default P1 has an even count (no warning); 기본간격 120 gives an odd count and a CP04 warning', () => {
|
||
const base = rebarSummary(buildPier(loadP1())).warnings.filter((w) => w.code === 'CP04');
|
||
expect(base).toEqual([]);
|
||
const odd = buildPier({ ...loadP1(), copingRebar: { mainSpacing: 120 } });
|
||
const warnings = rebarSummary(odd).warnings.filter((w) => w.code === 'CP04');
|
||
expect(warnings).toHaveLength(1);
|
||
expect(warnings[0].target).toBe('coping');
|
||
});
|
||
|
||
it('heights: 43.284 / 43.184 / 43.084 for the three top rows (상면 단 간격 100)', () => {
|
||
expect(topRowEl(43.284, 1)).toBe(43.284);
|
||
expect(topRowEl(43.284, 2)).toBe(43.184);
|
||
expect(topRowEl(43.284, 3)).toBe(43.084);
|
||
});
|
||
|
||
it('top bar lengths 12744 / 12686.8 / 12629.6 (path + 2 × 472 hook, 가로 단 간격 d = 28.6)', () => {
|
||
expect(topBarLength(12000, 1)).toBeCloseTo(12744, 0);
|
||
expect(topBarLength(12000, 2)).toBeCloseTo(12686.8, 1);
|
||
expect(topBarLength(12000, 3)).toBeCloseTo(12629.6, 1);
|
||
});
|
||
|
||
it('bottom piece ≈ 7448 with l_s = 1717.6 (1.3 × l_d of H29 at fck 27) — the document shows 1717', () => {
|
||
const b = bottomPiece();
|
||
expect(b.path).toBeCloseTo(12235.6, 6);
|
||
expect(b.ls).toBeCloseTo(1717.6, 0);
|
||
expect(b.piece).toBeCloseTo(7448, 0);
|
||
});
|
||
|
||
it('hook 472 (16.5 × 28.6) and splice from the same formula as C1', () => {
|
||
expect(hookLength('main90', NOMINAL_DIAMETER.H29)).toBeCloseTo(472, 0);
|
||
expect(spliceLength(NOMINAL_DIAMETER.H29, 400, 27)).toBeCloseTo(1.3 * developmentLength(NOMINAL_DIAMETER.H29, 400, 27), 9);
|
||
});
|
||
});
|