Files
houseplan-card/test/floor-geometry-key.test.mjs
T
Claude bf771c320b perf(summary): memoise the total area per floor by its record content (#769)
The summary panel's Total room area was memoised on the global config epoch:
any `_saveConfig()` or adopted server config — a rename on one floor, a
device or a setting — made the memo stale and the next View render ran
`cleanFloorAreaSteps` over every floor again (71 portions on large-house).

The area of a floor reads only its stored record, the model built from its
own record and constants, so each floor's value is now memoised by the
content key of those records. The key comes from the helper extracted out of
the #744 reader (`floorRecordKeyMemo`, src/floor-geometry-key.ts): one
fingerprint per floor and epoch, both records when a preview or a duplicate
id makes them differ, and independent of the shown floor. The total is the
sum of the floor values in `models` order, summed anew on every pass, so it
stays bitwise equal to a fresh `totalCleanFloorAreaM2`. A failed floor is
memoised as `null` until its record changes; the freshness check stays one
epoch comparison; the #509 portions, skeleton and the old value on screen
during a recomputation are unchanged; a lifecycle reset clears the memo.

The furniture and stairs magnet keeps its epoch key by measurement (its own
edits change the floor record anyway); the decision is recorded at the key.

Unit: test/summary-panel-area-memo.test.mjs (AC1–AC3 on a three-floor fixture
with walls, a door, a partition, a column and a stair; floors counted through
the injected geometry and portions through the step generator), and
test/floor-geometry-key.test.mjs for the shared key helper, now in the test
build. Two registry mutants (epoch key, rooms-only key); the #744 and #509
anchors follow the moved lines.

Issue: #769
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-07 05:21:35 +03:00

69 lines
3.1 KiB
JavaScript

// #744/#769: the content key of a floor's records. `floorRecordKeyMemo` is the
// helper shared by the card's floor-geometry reader (#744) and the summary
// panel's per-floor area (#769).
import assert from 'node:assert/strict';
import test from 'node:test';
import { floorGeometryKeyReader, floorRecordKeyMemo } from '../test-build/floor-geometry-key.js';
import { contentFingerprint } from '../test-build/visual-continuity.js';
const record = (id, cm = 15) => ({
id, cell_cm: 5, rooms: [{ id: `${id}1`, poly: [[0.1, 0.1], [0.5, 0.1], [0.5, 0.5]] }],
walls: [{ key: 'k', cm, a: [0.1, 0.1], b: [0.5, 0.1] }],
});
const spied = () => {
const calls = [];
const keyOf = floorRecordKeyMemo((value) => {
calls.push(value);
return contentFingerprint(value);
});
return { calls, keyOf };
};
test('#769 record key: one record is fingerprinted alone, two different objects together', () => {
const { keyOf } = spied();
const a = record('A');
assert.equal(keyOf(1, 's', 'A', a, a), `A|${contentFingerprint(a)}`);
const preview = record('A', 30);
assert.equal(keyOf(1, 't', 'A', preview, a), `A|${contentFingerprint([preview, a])}`,
'a preview record and the stored one: both enter the key');
const twin = structuredClone(a);
assert.equal(keyOf(1, 'u', 'A', twin, a), `A|${contentFingerprint([twin, a])}`,
'two objects are two records even with equal content');
assert.notEqual(keyOf(1, 'v', 'A', record('A', 20), a), keyOf(1, 't', 'A', preview, a),
'the content of the second record moves the key');
});
test('#769 record key: remembered per epoch and per record object', () => {
const { calls, keyOf } = spied();
const a = record('A');
const first = keyOf(1, '0', 'A', a, a);
for (let index = 0; index < 50; index++) assert.equal(keyOf(1, '0', 'A', a, a), first);
assert.equal(calls.length, 1, 'the same epoch and objects: one fingerprint');
const copy = structuredClone(a);
assert.equal(keyOf(1, '0', 'A', copy, copy), first, 'a new object with the same content: the same key');
assert.equal(calls.length, 2, '… after one more fingerprint');
assert.equal(keyOf(2, '0', 'A', copy, copy), first);
assert.equal(calls.length, 3, 'a new epoch fingerprints again');
keyOf(2, '1', 'B', record('B'), record('B'));
assert.equal(keyOf(2, '0', 'A', copy, copy), first, 'slots are separate');
assert.equal(calls.length, 4);
});
test('#769 record key does not depend on the shown floor; the #744 reader keys a foreign floor with it', () => {
const a = record('A');
const b = record('B');
const source = { _cfgEpoch: 1, _renderCfg: { spaces: [a, b] }, _curSpaceCfg: a };
const reader = floorGeometryKeyReader(source);
const { keyOf } = spied();
const own = keyOf(1, '1', 'B', b, b);
assert.equal(reader('A'), keyOf(1, '0', 'A', a, a), 'the shown floor: its own record');
assert.equal(reader('B'), `B|${contentFingerprint([b, a])}`,
'#744: a foreign floor is read next to the shown floor\'s record');
source._curSpaceCfg = b;
assert.equal(reader('B'), own, 'shown, B gets the record key');
source._curSpaceCfg = a;
assert.equal(keyOf(1, '1', 'B', b, b), own, 'the record key of B is the same whichever floor is shown');
});