Files
houseplan-card/test/near-axis.test.mjs
Claude 47469bab22 Монолит: мёртвый код снят по noUnusedLocals, связность измеряется шестью числами и гейтом (#624)
Карточка и редакторский рантайм держали ≈380 неиспользуемых импортов, 56
мёртвых объявлений и дублей типов (warm-boot, LS_*, GLOW_*, debounce,
navigate, lruRead — копии карточки в рантайме) и 112 приватных членов
карточки, которых не читал никто — делегаты `_editorRuntimeOrThrow()._x()`,
оставшиеся от выноса #425, и аксессоры glow-состояния. Всё это снято; в 9
других файлах — по одиночной ошибке. Делегаты и поля, которых касаются
браузерные смоки (`card._x(...)`), оставлены и посчитаны отдельно.

Гейт `npm run lint:unused` (scripts/unused-locals-gate.mjs, в gate:small и
Validate после сборки): `tsc --noUnusedLocals` чист, кроме приватных членов
карточки из порта HouseplanEditorHostPort / `host.` (portPrivates) и членов,
которых зовёт харнесс (harnessPrivates); храповик по шести числам
scripts/monolith-metrics.mjs против scripts/monolith-baseline.json —
delegates 260→159, portMembers 350, hostRefs 4948, portPrivates 96,
harnessPrivates 107, bundleBytes 2 510 141→2 500 387. `npm run inventory`
печатает те же числа. Заморозка 54 тестов, читающих монолит как текст
(test/monolith-text-anchors.test.mjs); PROCESS.md §2.7 — правило.

Логический исходник для контрактных тестов (test/houseplan-source.mjs)
дописывает члены рантайма без делегата в карточке — контракт продукта не
зависит от наличия заглушки. Потолки ядер и initial gzip опущены на выигрыш
(292 000 → 290 400). Бандл пересобран, три копии синхронны.

Issue: #624
User-Visible: no
2026-09-23 21:09:44 +03:00

147 lines
6.8 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import {
NEAR_AXIS_MAX_DEGREES,
NEAR_AXIS_MAX_SLOPE,
classifyNearAxisSegment,
repairNearAxisRoomWalls,
snapNearAxisEndpoint,
} from '../test-build/near-axis.js';
import { optimizePlans } from '../test-build/plan-optimizer.js';
import { MULTI_WALL_NEAR_ORTHOGONAL_MAX_DEGREES } from '../test-build/wall-thickness.js';
import { nearAxisProfile } from '../scripts/model-invariants.mjs';
import { readHouseplanProductionSource } from './houseplan-source.mjs';
const fixture = JSON.parse(readFileSync(
new URL('./fixtures/279-near-orthogonal-junction.json', import.meta.url), 'utf8',
));
const clone = (value) => JSON.parse(JSON.stringify(value));
const sourceOf = (file) => readFileSync(new URL(`../src/${file}`, import.meta.url), 'utf8');
test('#290 authoring, Optimize and renderer use one near-axis threshold source', () => {
const renderer = sourceOf('wall-thickness.ts');
const authoring = readHouseplanProductionSource();
const optimizer = sourceOf('plan-optimizer.ts');
assert.match(renderer, /import \{ NEAR_AXIS_MAX_DEGREES \} from '\.\/near-axis';/);
assert.match(renderer,
/export const MULTI_WALL_NEAR_ORTHOGONAL_MAX_DEGREES = NEAR_AXIS_MAX_DEGREES;/);
// #624: импорт живёт в редакторском рантайме — карточка держала мёртвую копию.
assert.match(sourceOf('houseplan-editor-runtime.ts'), /import \{ snapNearAxisEndpoint \} from '\.\/near-axis';/);
assert.match(authoring, /snapNearAxisEndpoint\(/);
assert.match(optimizer, /import \{ repairNearAxisRoomWalls \} from '\.\/near-axis';/);
assert.match(optimizer, /repairNearAxisRoomWalls\(space\)/);
});
test('#290 near-axis boundary is shared, inclusive and bounded', () => {
assert.equal(NEAR_AXIS_MAX_DEGREES, 0.25);
assert.equal(MULTI_WALL_NEAR_ORTHOGONAL_MAX_DEGREES, NEAR_AXIS_MAX_DEGREES);
assert.equal(classifyNearAxisSegment([0, 0], [316, 0]), null);
assert.equal(classifyNearAxisSegment([0, 0], [316, 1])?.axis, 'horizontal');
assert.equal(classifyNearAxisSegment([0, 0], [316, 2]), null);
assert.equal(classifyNearAxisSegment([0, 0], [1, NEAR_AXIS_MAX_SLOPE])?.axis, 'horizontal');
assert.equal(classifyNearAxisSegment([0, 0], [1, NEAR_AXIS_MAX_SLOPE * 1.000001]), null);
assert.equal(classifyNearAxisSegment([0, 0], [1, Math.tan(Math.PI / 6)]), null);
assert.equal(classifyNearAxisSegment([0, 0], [1, 316])?.axis, 'vertical');
assert.equal(classifyNearAxisSegment([1, 316], [0, 0])?.axis, 'vertical');
});
test('#290 Walls rule moves only the free endpoint', () => {
assert.deepEqual(snapNearAxisEndpoint([10, 20], [326, 21]), [326, 20]);
assert.deepEqual(snapNearAxisEndpoint([10, 20], [11, 336]), [10, 336]);
assert.deepEqual(snapNearAxisEndpoint([10, 20], [326, 22]), [326, 22]);
assert.deepEqual(snapNearAxisEndpoint([10, 20], [110, 120]), [110, 120]);
});
test('#290 repairs a duplicated 316x1 physical wall once without mutating input', () => {
const input = clone(fixture);
const before = JSON.stringify(input);
const result = repairNearAxisRoomWalls(input);
assert.equal(JSON.stringify(input), before);
assert.equal(result.report.wallsStraightened, 1);
assert.equal(result.report.wallsStraightenSkipped, 0);
assert.ok(Math.abs(result.report.maxStraightenShift - 1 / 240) < 1e-9);
const north = result.space.rooms.find((room) => room.id === 'north-west');
const south = result.space.rooms.find((room) => room.id === 'south-west');
assert.deepEqual(north.poly[1], south.poly[0]);
assert.deepEqual(north.poly[2], south.poly[3]);
assert.equal(north.poly[1][1], north.poly[2][1]);
});
test('#290 repairs ordinary independent walls but preserves true diagonals', () => {
const input = {
rooms: [],
partitions: [
{ id: 'former-draft-a', a: [0, 0], b: [316, 1], cm: 15 },
{ id: 'former-draft-b', a: [316, 1], b: [316, 20], cm: 15 },
{ id: 'near', a: [0, 100], b: [316, 101], cm: 15 },
{ id: 'diagonal', a: [0, 200], b: [100, 300], cm: 15 },
],
};
const result = repairNearAxisRoomWalls(input);
assert.equal(result.report.wallsStraightened, 2);
assert.equal(result.space.partitions[0].a[1], result.space.partitions[0].b[1]);
assert.deepEqual(result.space.partitions[1], input.partitions[1]);
assert.equal(result.space.partitions[2].a[1], result.space.partitions[2].b[1]);
assert.deepEqual(result.space.partitions[3], input.partitions[3]);
});
test('#290 skips a repair that would no longer fit a hosted opening', () => {
const input = {
rooms: [],
partitions: [{ id: 'host', a: [0, 0], b: [316, 1], cm: 15 }],
openings: [{
id: 'opening',
length: 316.001,
host: { kind: 'partition', id: 'host', t: 0.5 },
}],
};
const result = repairNearAxisRoomWalls(input);
assert.equal(result.report.wallsStraightened, 0);
assert.equal(result.report.wallsStraightenSkipped, 1);
assert.deepEqual(result.space.partitions, input.partitions);
});
test('#290 Optimize reports physical count, exact max centimetres and is idempotent', () => {
const config = { spaces: [{ id: 'floor', title: 'Floor', ...clone(fixture) }] };
const first = optimizePlans(config, {});
assert.equal(first.report.wallsStraightened, 1);
assert.equal(first.report.wallsStraightenSkipped, 0);
assert.ok(Math.abs(first.report.maxStraightenShiftCm - 1) < 1e-6);
assert.equal(first.report.maxStraightenSpace, 'floor');
assert.equal(first.changed, true);
const second = optimizePlans(first.config, first.layout);
assert.equal(second.changed, false);
assert.equal(second.report.wallsStraightened, 0);
assert.equal(second.report.maxStraightenShiftCm, 0);
});
test('#290 Optimize reduces the tracked real-plan near-axis profile and stays idempotent', () => {
for (const file of ['real-plan-first-floor.json', 'real-plan-second-floor.json']) {
const { space } = JSON.parse(readFileSync(
new URL(`./fixtures/${file}`, import.meta.url), 'utf8',
));
const config = { spaces: [clone(space)] };
const before = nearAxisProfile(config);
const first = optimizePlans(config, {});
const after = nearAxisProfile(first.config);
const second = optimizePlans(first.config, first.layout);
assert.equal(after.total, before.total - first.report.wallsStraightened, file);
assert.equal(after.total <= before.total, true, file);
assert.equal(second.report.wallsStraightened, 0, file);
assert.equal(nearAxisProfile(second.config).total, after.total, file);
}
const { space } = JSON.parse(readFileSync(
new URL('./fixtures/real-plan-second-floor.json', import.meta.url), 'utf8',
));
const config = { spaces: [clone(space)] };
const before = nearAxisProfile(config);
const repaired = optimizePlans(config, {});
assert.equal(before.total, 1);
assert.equal(repaired.report.wallsStraightened, 1);
assert.equal(nearAxisProfile(repaired.config).total, 0);
});