mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-07 15:09:30 +00:00
fix(perf): name the measured profile in the 2.5D runner contract errors (#770)
Three profiles walk the 2.5D candidate contract of the large-house runner - large-house-isometric-v1, its Stage 3 dense twin and the backdrop twin of #743 - but both of its errors said "large-house-isometric-v1 candidate has no ...", so a dense or backdrop failure pointed at the wrong profile and job. The two checks move into assertIsometricCandidate(card, profile, stage) in demo/performance/card-contract.mjs, self-contained like assertCardContract and injected the same way with toString(); the runner calls it with the profile it measures for the pre-#448 Labs hook and for the renderer contract. Conditions and wording are otherwise unchanged, so a base or candidate that passed before passes now. The unit test runs both the export and its serialized copy for all three 2.5D profiles and requires "<profile> candidate has no Labs fixture hook" and "<profile> candidate has no renderer contract"; it also pins that the runner injects and calls it and no longer names one profile for all. Red on the previous runner (the anchor), and red when the message is hardcoded back to large-house-isometric-v1 (dense profile). Issue: #770 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
@@ -8,7 +8,11 @@ import { LARGE_HOUSE_COUNTS, makeLargeHouseFixture } from './fixtures/large-hous
|
||||
import { assertFreshDemoBundle } from './bundle-freshness.mjs';
|
||||
import { ensureHarnessEditorRuntime } from './editor-runtime-compat.mjs';
|
||||
import { summarizeLongTasks, summarizeTimings } from './performance/evaluate.mjs';
|
||||
import { assertCardContract, LARGE_HOUSE_CARD_CONTRACT } from './performance/card-contract.mjs';
|
||||
import {
|
||||
assertCardContract,
|
||||
assertIsometricCandidate,
|
||||
LARGE_HOUSE_CARD_CONTRACT,
|
||||
} from './performance/card-contract.mjs';
|
||||
import {
|
||||
ISOMETRIC_STAGE3_DENSE_PROFILE,
|
||||
makeIsometricStage3DenseFixture,
|
||||
@@ -96,6 +100,9 @@ await page.addStyleTag({
|
||||
await page.addScriptTag({
|
||||
content: `window.__hpAssertCardContract = ${assertCardContract.toString()};`,
|
||||
});
|
||||
await page.addScriptTag({
|
||||
content: `window.__hpAssertIsometricCandidate = ${assertIsometricCandidate.toString()};`,
|
||||
});
|
||||
await page.addScriptTag({
|
||||
content: `window.__hpEnsureHarnessEditorRuntime = ${ensureHarnessEditorRuntime.toString()};`,
|
||||
});
|
||||
@@ -395,8 +402,7 @@ try {
|
||||
const loadStarted = performance.now();
|
||||
host.replaceChildren(card);
|
||||
if (requiresIsometric && window.__hpAlpha !== true) {
|
||||
if (typeof card._onLabsSnapshot !== 'function')
|
||||
throw new Error('large-house-isometric-v1 candidate has no Labs fixture hook');
|
||||
window.__hpAssertIsometricCandidate(card, profile, 'labs-hook');
|
||||
// Comparison bundles before #448 do not understand hp_alpha. Preserve
|
||||
// the cross-version benchmark only for that base; current candidates
|
||||
// must activate through the real URL/storage contract above.
|
||||
@@ -410,10 +416,7 @@ try {
|
||||
throw new Error('large-house editor runtime did not preload');
|
||||
if (isometric) await ensureIsoRuntime(card);
|
||||
window.__hpAssertCardContract(card, cardContract);
|
||||
if (requiresIsometric && (typeof card._effectiveProjection !== 'function'
|
||||
|| !(card._isoGeometryCache instanceof Map))) {
|
||||
throw new Error('large-house-isometric-v1 candidate has no renderer contract');
|
||||
}
|
||||
if (requiresIsometric) window.__hpAssertIsometricCandidate(card, profile, 'renderer');
|
||||
await until(() => card._loadOk && card._model?.length === fixture.counts.floors);
|
||||
await card.updateComplete;
|
||||
await frame();
|
||||
|
||||
@@ -160,6 +160,26 @@ export const SPACE_GLOW_CARD_CONTRACT = Object.freeze({
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* The 2.5D candidate contract of the large-house runner, shared by every 2.5D
|
||||
* profile: `large-house-isometric-v1`, its Stage 3 dense twin and the backdrop
|
||||
* twin (#743). The error names the profile being measured (#770); it used to
|
||||
* name the historical profile whichever one failed. `labs-hook` is the
|
||||
* pre-#448 activation path, checked before the first `hass`; `renderer` runs
|
||||
* after the lazy renderer has loaded. Self-contained like
|
||||
* `assertCardContract`: the runner serializes it with `toString()`.
|
||||
*/
|
||||
export function assertIsometricCandidate(card, profile, stage) {
|
||||
if (stage === 'labs-hook') {
|
||||
if (typeof card._onLabsSnapshot !== 'function')
|
||||
throw new Error(`${profile} candidate has no Labs fixture hook`);
|
||||
return;
|
||||
}
|
||||
if (stage !== 'renderer') throw new Error(`unknown 2.5D contract stage: ${stage}`);
|
||||
if (typeof card._effectiveProjection !== 'function' || !(card._isoGeometryCache instanceof Map))
|
||||
throw new Error(`${profile} candidate has no renderer contract`);
|
||||
}
|
||||
|
||||
/** Single fail-fast implementation injected into both browser runners. Keep
|
||||
* this function self-contained: runners serialize it with `toString()`. */
|
||||
export function assertCardContract(card, contract) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import {
|
||||
assertCardContract,
|
||||
assertIsometricCandidate,
|
||||
GLOW_CARD_CONTRACT,
|
||||
LARGE_HOUSE_CARD_CONTRACT,
|
||||
SPACE_GLOW_CARD_CONTRACT,
|
||||
@@ -164,6 +165,37 @@ test('large-house contract accepts only an explicit current or stable resize own
|
||||
);
|
||||
});
|
||||
|
||||
test('#770: 2.5D contract errors name the measured profile, not the historical one', () => {
|
||||
const runner = readFileSync(new URL('../demo/benchmark_large_house.mjs', import.meta.url), 'utf8');
|
||||
// Every 2.5D profile the runner accepts walks this contract.
|
||||
const profiles = ['large-house-isometric-v1', 'isometric-stage3-dense-v1', 'large-house-isometric-backdrop-v1'];
|
||||
// The runner injects the function with `toString()`: the serialized copy must
|
||||
// behave exactly like the module export, so it is judged too.
|
||||
const injected = new Function(`return ${assertIsometricCandidate.toString()}`)();
|
||||
const complete = {
|
||||
_onLabsSnapshot: () => undefined, _effectiveProjection: () => 'iso', _isoGeometryCache: new Map(),
|
||||
};
|
||||
for (const check of [assertIsometricCandidate, injected]) {
|
||||
for (const profile of profiles) {
|
||||
assert.throws(() => check({}, profile, 'labs-hook'),
|
||||
(error) => error.message === `${profile} candidate has no Labs fixture hook`);
|
||||
assert.throws(() => check({ ...complete, _isoGeometryCache: {} }, profile, 'renderer'),
|
||||
(error) => error.message === `${profile} candidate has no renderer contract`);
|
||||
assert.throws(() => check({ ...complete, _effectiveProjection: undefined }, profile, 'renderer'),
|
||||
(error) => error.message === `${profile} candidate has no renderer contract`);
|
||||
assert.doesNotThrow(() => check(complete, profile, 'labs-hook'));
|
||||
assert.doesNotThrow(() => check(complete, profile, 'renderer'));
|
||||
}
|
||||
assert.throws(() => check(complete, 'isometric-stage3-dense-v1', 'renderr'), /unknown 2\.5D contract stage: renderr/);
|
||||
}
|
||||
// The runner injects it, calls both stages with the profile it measures and
|
||||
// keeps no message of its own that names one profile for all three.
|
||||
assert.match(runner, /window\.__hpAssertIsometricCandidate = \$\{assertIsometricCandidate\.toString\(\)\};/);
|
||||
assert.match(runner, /window\.__hpAssertIsometricCandidate\(card, profile, 'labs-hook'\);/);
|
||||
assert.match(runner, /if \(requiresIsometric\) window\.__hpAssertIsometricCandidate\(card, profile, 'renderer'\);/);
|
||||
assert.doesNotMatch(runner, /large-house-isometric-v1 candidate has no/);
|
||||
});
|
||||
|
||||
test('contract accepts recent optional fields only when their runtime type is valid', () => {
|
||||
const base = {
|
||||
_cleanFloorCache: new Map(), _glowClipCache: new Map(),
|
||||
|
||||
Reference in New Issue
Block a user