import * as THREE from 'three'; import { describe, expect, it } from 'vitest'; import { defaultJointCount, jointCount, jointPositions } from '../src/core/abut/joints'; import { buildAbut } from '../src/core/abut/geometry'; import { parseAbutments, toAbutInput } from '../src/core/abut/parse'; import { buildMembers } from '../src/core/abut/members'; import { buildAbutGroup } from '../src/view/abut-scene'; import { loadInputWorkbook } from './helpers'; // 교대 수축줄눈 (spec_abut §3 · §4.9, QA-7): count ceil(L/10) − 1, position = the middle between the nearest bearings. const rec = parseAbutments(loadInputWorkbook()).abutments.find((a) => a.name === 'A1')!; const model = buildAbut(toAbutInput(rec)); const bearingS = model.bearings.map((b) => b.s); describe('교대 수축줄눈', () => { it('count: ceil(L/10) − 1 by default (12.197 → 1, 20 → 1, 20.5 → 2, 9 → 0), a typed count wins', () => { expect([12.197, 20, 20.5, 9].map(defaultJointCount)).toEqual([1, 1, 2, 0]); expect(jointCount(12.197, 3)).toBe(3); }); it('A1: the joint is not on bearing ③ (L/2) but in the middle between ② and ③, which is the 교좌면 step boundary', () => { const [s] = jointPositions(model.length, bearingS); const sorted = [...bearingS].sort((a, b) => a - b); expect(s).toBeCloseTo((sorted[2] + sorted[3]) / 2, 9); expect(Math.min(...bearingS.map((b) => Math.abs(b - s)))).toBeGreaterThan(0.6 + 0.6); expect(model.stepBounds.some((x) => Math.abs(x - s) < 1e-3)).toBe(true); }); it('several joints take distinct midpoints nearest to the even split; without enough bearings, the even split', () => { const js = jointPositions(model.length, bearingS, 2); expect(new Set(js.map((x) => x.toFixed(6))).size).toBe(2); expect(jointPositions(30, [5], 2)).toEqual([10, 20]); }); it('3D: one line on each of the four 벽체 · 흉벽 faces per joint, none in the 저판', () => { const members = buildMembers(model, { bottomEl: rec.foundationBottomEl, pileLengthMm: rec.pileLengthMm }); const group = buildAbutGroup(model, members); const lines = group.getObjectByName('joints')!.children[0] as THREE.LineSegments; const pos = lines.geometry.getAttribute('position') as THREE.BufferAttribute; expect(pos.count).toBe(4 * 2); for (let i = 0; i < pos.count; i++) expect(pos.getY(i)).toBeGreaterThanOrEqual(members.footingTopEl - 1e-5); }); });