- 선 스타일을 삽도에 맞춤: B5–B8 청록 실선, B1–B4 주황 점, B9 보라 세로선, B10 녹색 점, B11 회색 파선, B12 회색 점 - 범례 5항목 (선 모양별) - 하단 치수선(피복 · 격자 e · n@s=G · e)과 우측 높이 치수선(상면 피복 · B10 단 · 하면 피복), 화살 끝 표시 - B6 다리 주석 "B6 다리 → B5 높이 (값)" - 기둥 블록(상부)과 말뚝 머리(하부)를 맥락 그림으로 표시 - 높이 방향을 실제 비율로 그림 (축소 없음) - 탭 4개 중 "교각축 직각 단면 (폭 W)"만 작동, 나머지는 다음 단계 자리로 비활성 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
69 lines
3.6 KiB
TypeScript
69 lines
3.6 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import { buildPier } from '../src/core/geometry';
|
||
import { assemblyBars, assemblyGridIndices, assemblyJs, b9Bars, bottomBars, crossBars, sideBars } from '../src/core/rebar/footing';
|
||
import { DEFAULT_FOOTING_REBAR } from '../src/core/rebar/footing-settings';
|
||
import { footingSectionSvg } from '../src/view/footing-section-svg';
|
||
import { loadP1 } from './helpers';
|
||
|
||
const countMark = (svg: string, cls: string, bar: string) => svg.split(`class="${cls}" data-bar="${bar}"`).length - 1;
|
||
|
||
describe('확대기초 교각축 직각 단면 (폭 W) (승인 2026-10-26, 추천안 1단계)', () => {
|
||
const m = buildPier(loadP1());
|
||
const foot = m.footings[0];
|
||
const L = foot.length * 1000;
|
||
const W = foot.width * 1000;
|
||
const H = (foot.top - foot.bottom) * 1000;
|
||
const fs = DEFAULT_FOOTING_REBAR;
|
||
const svg = footingSectionSvg(m);
|
||
|
||
it('point and line counts match the rebar table families', () => {
|
||
const bottom = bottomBars(L, W, H, fs);
|
||
expect(countMark(svg, 'b1', 'B1')).toBe(bottom.find((b) => b.name === 'B1')!.count);
|
||
expect(countMark(svg, 'b2', 'B2')).toBe(bottom.find((b) => b.name === 'B2')!.count);
|
||
expect(countMark(svg, 'b4', 'B4')).toBe(bottom.find((b) => b.name === 'B4')!.count);
|
||
expect(countMark(svg, 'b9', 'B9')).toBe(b9Bars(L, W, [], 2000, fs).rows);
|
||
expect(countMark(svg, 'b10', 'B10-1')).toBe(sideBars(L, W, H, fs).find((b) => b.name === 'B10-1')!.count);
|
||
expect(countMark(svg, 'b11', 'B11')).toBe(assemblyJs(H, fs).length);
|
||
expect(countMark(svg, 'b12', 'B12')).toBe(assemblyBars(L, W, H, fs).find((b) => b.name === 'B12')!.count);
|
||
for (const name of ['B5', 'B6', 'B7', 'B8']) expect(countMark(svg, 'b5', name)).toBe(1);
|
||
expect(crossBars(L, W, H, fs).length).toBe(4);
|
||
});
|
||
|
||
it('shows the width, the grid string, and the B10 levels as dimensions', () => {
|
||
expect(svg).toContain('폭 8150');
|
||
expect(svg).toContain('8@200=1600');
|
||
expect(svg).toContain(`${fs.cBottom}`);
|
||
});
|
||
|
||
it('has one bubble per highlighted bar family', () => {
|
||
for (const name of ['B1', 'B2', 'B3', 'B4', 'B5', 'B11', 'B10-1']) expect(svg).toContain(`data-bubble="${name}"`);
|
||
});
|
||
|
||
it('조립 철근은 격자 가운데에 놓임: 양끝 여유 차이 ≤ 1칸 (B11 n 79, B12 n 61, k 8, 엇갈림 2칸 유지)', () => {
|
||
const levels = assemblyJs(H, fs).length;
|
||
for (const [n, k] of [[79, 8], [61, 8]] as const) {
|
||
const all = Array.from({ length: levels }, (_, idx) => assemblyGridIndices(n, k, idx)).flat();
|
||
const left = Math.min(...all);
|
||
const right = n - Math.max(...all);
|
||
expect(Math.abs(left - right)).toBeLessThanOrEqual(1);
|
||
}
|
||
expect(assemblyGridIndices(61, 8, 1)[0] - assemblyGridIndices(61, 8, 0)[0]).toBe(2);
|
||
});
|
||
|
||
it('조립 철근(B11·B12)은 짝수 격자에 놓여 B9(홀수 격자)와 겹치지 않음 (확대기초철근 (1).md §9.1)', () => {
|
||
const levels = assemblyJs(H, fs).length;
|
||
for (const [n, k] of [[79, 8], [61, 8]] as const) {
|
||
for (let idx = 0; idx < levels; idx++) {
|
||
for (const i of assemblyGridIndices(n, k, idx)) expect(i % 2).toBe(0);
|
||
}
|
||
}
|
||
});
|
||
|
||
it('2단계: B6 다리 주석 1723, 범례 5항목, 기둥과 말뚝 맥락 표시, 탭 4개 중 첫 탭만 작동', () => {
|
||
expect(svg).toContain('B6 다리 → B5 높이 1723');
|
||
for (const label of ['주철근 (B5–B8)', '직각·보조 (B1–B4)', '전단·띠 (B9)', '측면 수평 (B10-1 · B10-2)', '조립 (B11·B12)']) expect(svg).toContain(label);
|
||
expect(svg).toContain('class="column"');
|
||
expect(svg.split('class="pile"').length - 1).toBeGreaterThan(0);
|
||
});
|
||
});
|