mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
fix: keep jamb margin on partition openings
Issue: #186 User-Visible: yes
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
openingPlacementTargets,
|
||||
passagePlacementPreviewGeometry,
|
||||
resolveOpeningPlacement,
|
||||
resolveOpeningPlacementResult,
|
||||
sameOpeningPlacementInput,
|
||||
} from '../test-build/opening-placement.js';
|
||||
|
||||
@@ -43,6 +44,46 @@ test('Opening presets use the agreed type-specific defaults', () => {
|
||||
assert.equal(openingPlacementPreset('gate', 3).lengthCm, 300);
|
||||
});
|
||||
|
||||
test('partition placement reserves its physical half-width at both endpoints', () => {
|
||||
const hosted = interval({
|
||||
partitionHost: { kind: 'partition', id: 'p1' },
|
||||
half: 10,
|
||||
});
|
||||
const left = resolve({ pointer: [0, 0], intervals: [hosted] });
|
||||
const right = resolve({ pointer: [100, 0], intervals: [hosted] });
|
||||
assert.equal(left.x, 25);
|
||||
assert.equal(left.host.t, 0.25);
|
||||
assert.equal(left.measure.labels[0].distance, 10);
|
||||
assert.equal(right.x, 75);
|
||||
assert.equal(right.host.t, 0.75);
|
||||
assert.equal(right.measure.labels[1].distance, 10);
|
||||
});
|
||||
|
||||
test('a partition shorter than opening plus both jambs reports a typed block', () => {
|
||||
const result = resolveOpeningPlacementResult({
|
||||
pointer: [24.5, 0],
|
||||
preset: openingPlacementPreset('door', 1),
|
||||
geometryRevision: 7,
|
||||
renderedLength: 30,
|
||||
intervals: [interval({
|
||||
b: [49, 0], half: 10,
|
||||
partitionHost: { kind: 'partition', id: 'short' },
|
||||
})],
|
||||
baseTolerance: 4,
|
||||
bodyPointerPadding: 2,
|
||||
gridStep: 10,
|
||||
});
|
||||
assert.equal(result.candidate, null);
|
||||
assert.equal(result.jambBlockedTarget.partitionHost.id, 'short');
|
||||
assert.equal(result.jambBlockedTarget.physicalHalfWidth, 10);
|
||||
});
|
||||
|
||||
test('room-wall placement keeps its zero-jamb endpoint contract', () => {
|
||||
const candidate = resolve({ pointer: [0, 0], intervals: [interval({ half: 10 })] });
|
||||
assert.equal(candidate.x, 15);
|
||||
assert.equal(candidate.measure.labels[0].distance, 0);
|
||||
});
|
||||
|
||||
test('passage preview geometry follows the resolved length and standard wall depth', () => {
|
||||
const geometry = passagePlacementPreviewGeometry({
|
||||
renderedLength: 90,
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
partitionOpeningCut,
|
||||
partitionOpeningFace,
|
||||
partitionOpeningHasCompositeRoomWall,
|
||||
partitionOpeningJambMargin,
|
||||
partitionOpeningNeedsStrictValidation,
|
||||
resolvePartitionOpening,
|
||||
resolvePartitionOpeningStrict,
|
||||
} from '../test-build/partition-openings.js';
|
||||
import { cutPartitionBody, partitionBody } from '../test-build/physical-geometry.js';
|
||||
import { physicalBodyParts } from '../test-build/physical-geometry.js';
|
||||
@@ -49,6 +52,55 @@ test('missing, out-of-range and too-short hosts fail dark', () => {
|
||||
'does-not-fit');
|
||||
});
|
||||
|
||||
test('strict writes reserve half the real wall depth while compat reads remain visible', () => {
|
||||
assert.equal(partitionOpeningJambMargin(partition, 5, 5), 7.5);
|
||||
for (const type of ['door', 'window', 'gate', 'passage']) {
|
||||
const atBoundary = opening({ type, host: { kind: 'partition', id: 'p1', t: 0.225 } });
|
||||
const insideJamb = opening({ type, host: { kind: 'partition', id: 'p1', t: 0.224 } });
|
||||
assert.ok(resolvePartitionOpeningStrict(atBoundary, [partition], 100, 5, 5).resolved);
|
||||
assert.equal(
|
||||
resolvePartitionOpeningStrict(insideJamb, [partition], 100, 5, 5).reason,
|
||||
'does-not-fit-jamb',
|
||||
);
|
||||
assert.ok(resolvePartitionOpening(insideJamb, [partition], 100, 5, 5).resolved,
|
||||
`compat/read policy must not orphan a stored near-end ${type}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('strict jamb policy follows thickness, scale and host direction', () => {
|
||||
for (const cm of [1, 15, 100]) {
|
||||
for (const [cellCm, gridPitch] of [[5, 5], [2.5, 8]]) {
|
||||
for (const [a, b] of [[[0, 0], [1000, 0]], [[1000, 1000], [0, 0]]]) {
|
||||
const host = { id: 'p1', a, b, cm };
|
||||
const span = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
||||
const margin = partitionOpeningJambMargin(host, cellCm, gridPitch);
|
||||
const length = 200;
|
||||
const exactT = (length / 2 + margin) / span;
|
||||
const candidate = opening({
|
||||
length: length / 100,
|
||||
host: { kind: 'partition', id: 'p1', t: exactT },
|
||||
});
|
||||
assert.ok(resolvePartitionOpeningStrict(
|
||||
candidate, [host], 100, cellCm, gridPitch,
|
||||
).resolved, `${cm}/${cellCm}/${gridPitch} exact boundary`);
|
||||
assert.equal(resolvePartitionOpeningStrict({
|
||||
...candidate, host: { ...candidate.host, t: exactT - 1e-5 },
|
||||
}, [host], 100, cellCm, gridPitch).reason, 'does-not-fit-jamb');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('only direct hosted geometry changes opt an existing opening into strict validation', () => {
|
||||
const original = opening();
|
||||
assert.equal(partitionOpeningNeedsStrictValidation(original, { ...original, type: 'window' }), false);
|
||||
assert.equal(partitionOpeningNeedsStrictValidation(original, {
|
||||
...original, host: { ...original.host, t: 0.4 },
|
||||
}), true);
|
||||
assert.equal(partitionOpeningNeedsStrictValidation(original, { ...original, length: 0.4 }), true);
|
||||
assert.equal(partitionOpeningNeedsStrictValidation(null, original), true);
|
||||
});
|
||||
|
||||
test('partition body is split by its own full-depth slot only', () => {
|
||||
const body = partitionBody(partition.a, partition.b, partition.cm, 5, 5);
|
||||
const resolved = resolvePartitionOpening(opening(), [partition], 100, 5, 5).resolved;
|
||||
|
||||
Reference in New Issue
Block a user