Files
Pier_v1/tests/coping-rebar-svg.test.ts
T
ypshimandClaude Haiku 5.5 d5bf02e36b feat(rebar): 단면·폐합 section with the top-row and side-row chains and bar-group bubbles
- Top row (C1–C8) spacing along the 교축 from chainDims, between the coping's
  two long edges (±B/2).
- Side rows (C12·C13) along the EL, from the coping top EL down to the lowest
  side bar.
- One bubble per bar group cut at the station, with a leader to its bars.

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

95 lines
4.3 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);
// A bubble is drawn for each group that is cut at this station, and only once.
for (const label of ['C1–6', 'C12·13', 'C10', 'C7·8', 'C14', 'C16']) {
expect(svg.split(`>${label}<`).length - 1).toBe(1);
}
});
});
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);
});
});