Files
Pier_v1/tests/coping-rebar-svg.test.ts
T
ypshimandClaude Haiku 5.5 64539c63d6 feat(rebar): 단면·폐합 section shows only C1–C8 and C12·C13, with the top spacing labelled
- Other bars (C9–C11, C14–C16) are left out of the section; bubbles only for C1–6, C7·8, C12·13.
- The top-row chain carries the label 상면 간격.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-26 15:09:38 +09:00

99 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest';
import { buildPier } from '../src/core/geometry';
import { buildRebar3D } from '../src/view/rebar3d';
import { chainDims, copingBarGroup, copingFrontSvg, copingSectionSvg } from '../src/view/coping-rebar-svg';
import { loadP1 } from './helpers';
const model = buildPier(loadP1());
const scene = buildRebar3D(model);
describe('코핑 배근 정면 · 단면 (승인 2026-10-26)', () => {
it('the 3D bar records include the coping bars, the same data as the meshes', () => {
const coping = scene.records.filter((r) => /^C\d/.test(r.name));
expect(coping.length).toBeGreaterThan(0);
expect(scene.records.length).toBeGreaterThan(coping.length - 1);
});
it('the front view draws one polyline per coping bar', () => {
const coping = scene.records.filter((r) => /^C\d/.test(r.name));
const svg = copingFrontSvg(scene.records, model.copingL * 1000);
expect((svg.match(/<polyline/g) ?? []).length).toBe(coping.length);
});
it('groups the bars: C1–C8 main, C9–C11 loops, C12–C13 sides, C14–C16 steps', () => {
expect(copingBarGroup('C1')).toBe('main');
expect(copingBarGroup('C9')).toBe('loop');
expect(copingBarGroup('C12')).toBe('side');
expect(copingBarGroup('C15')).toBe('step');
});
it('the section at mid-length shows the bars that cross it', () => {
const svg = copingSectionSvg(scene.records, model.copingL / 2);
expect((svg.match(/<circle/g) ?? []).length).toBeGreaterThan(0);
});
});
describe('단면 · 폐합 글자 겹침 (승인 2026-10-26)', () => {
it('writes each bubble label once', () => {
const svg = copingSectionSvg(scene.records, 6, model.copingRefTop, model.copingB * 1000);
// Only the top row and the side rows are shown here: their bubbles, once each.
for (const label of ['C1–6', 'C7·8', 'C12·13']) {
expect(svg.split(`>${label}<`).length - 1).toBe(1);
}
for (const label of ['C10', 'C14', 'C16']) {
expect(svg).not.toContain(`>${label}<`);
}
expect(svg).toContain('상면 간격');
});
});
describe('정면 번호 풍선과 치수 (승인 2026-10-26, 시안 ③)', () => {
const svg = copingFrontSvg(scene.records, model.copingL * 1000);
it('has one bubble per bar group, with its label', () => {
for (const label of ['C1–6', 'C12·13', 'C9', 'C10', 'C11', 'C7·8', 'C14', 'C15', 'C16']) {
expect(svg).toContain(`>${label}<`);
}
});
it('writes the 시안 length dimension (300 · 4500 · 2400 · 4500 · 300 = 12000)', () => {
expect(svg).toContain('18@250=4500');
expect(svg).toContain('8@300=2400');
expect(svg).toContain('코핑 길이 12000');
});
});
describe('정면 두 번째 치수선과 범례 (승인 2026-10-26, 시안)', () => {
const svg = copingFrontSvg(scene.records, model.copingL * 1000);
it('writes the column-centre span 10@1000=10000 and the legend', () => {
expect(svg).toContain('10@1000=10000');
expect(svg).toContain('기둥 중심');
expect(svg).toContain('주철근 (C1–C8)');
expect(svg).toContain('전단·띠 (C9–C11)');
});
});
describe('정면 치수는 3D 철근 위치에서 계산 (승인 2026-10-26)', () => {
it('the chains come from the bar positions and match the 시안 values', () => {
const svg = copingFrontSvg(scene.records, model.copingL * 1000, model.columns[0].s);
const texts = [...svg.matchAll(/font-size="220"[^>]*>([^<]*)</g)].map((x) => x[1]);
expect(texts).toEqual(['300', '18@250=4500', '8@300=2400', '18@250=4500', '300', '900', '10@1000=10000', '900']);
});
});
describe('chainDims', () => {
it('groups repeated gaps and writes a single gap as its length', () => {
expect(chainDims([300, 550, 800, 1100], 0, 1400).map((d) => d.text)).toEqual(['300', '2@250=500', '2@300=600']);
});
});
describe('아래 치수선의 0 = 기둥 중심 (승인 2026-10-26)', () => {
it('the column-centre tick sits at the middle of the 10@1000 span (the 10 intervals are centred on the column)', () => {
const col = model.columns[0].s * 1000;
const svg = copingFrontSvg(scene.records, model.copingL * 1000, model.columns[0].s);
expect(svg).toContain(`<line x1="${col}" y1=`);
const seg = [...svg.matchAll(/<line x1="([-\d.]+)" y1="[-\d.]+" x2="([-\d.]+)" y2="[-\d.]+" stroke="#18212B" stroke-width="10"/g)].map((m) => [Number(m[1]), Number(m[2])]);
const span = seg.find(([a, b]) => b - a > 9000 && b - a < 11000);
expect(span).toBeDefined();
expect((span![0] + span![1]) / 2).toBeCloseTo(col, 0);
});
});