mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
test: isolate version recovery reload guards
Issue: #462 User-Visible: no
This commit is contained in:
@@ -203,7 +203,8 @@ const MUTANT_DEFINITIONS = [
|
||||
},
|
||||
{
|
||||
id: 'version-recovery-ignores-editor-state',
|
||||
guard: 'node demo/smoke_version_recovery.mjs',
|
||||
guard: 'node --test --test-name-pattern="card adapter maps editor state" '
|
||||
+ 'test/version-recovery-card.test.mjs',
|
||||
because: 'a kiosk card in an editor must preserve unsaved work instead of silently reloading '
|
||||
+ 'when its versions differ (#462 AC8)',
|
||||
patches: [{
|
||||
@@ -214,7 +215,8 @@ const MUTANT_DEFINITIONS = [
|
||||
},
|
||||
{
|
||||
id: 'version-recovery-ignores-dialog-state',
|
||||
guard: 'node demo/smoke_version_recovery.mjs',
|
||||
guard: 'node --test --test-name-pattern="card adapter maps blocking surfaces" '
|
||||
+ 'test/version-recovery-card.test.mjs',
|
||||
because: 'a first-class dialog or contextual surface must block the kiosk auto-reload '
|
||||
+ 'rather than disappear underneath it (#462 AC8)',
|
||||
patches: [{
|
||||
@@ -225,7 +227,8 @@ const MUTANT_DEFINITIONS = [
|
||||
},
|
||||
{
|
||||
id: 'version-recovery-ignores-pending-config-write',
|
||||
guard: 'node demo/smoke_version_recovery.mjs',
|
||||
guard: 'node --test --test-name-pattern="card adapter maps config writes" '
|
||||
+ 'test/version-recovery-card.test.mjs',
|
||||
because: 'a pending configuration write must block kiosk reload so an accepted edit cannot '
|
||||
+ 'be lost during version recovery (#462 AC8)',
|
||||
patches: [{
|
||||
@@ -236,7 +239,8 @@ const MUTANT_DEFINITIONS = [
|
||||
},
|
||||
{
|
||||
id: 'version-recovery-ignores-interaction-pause',
|
||||
guard: 'node demo/smoke_version_recovery.mjs',
|
||||
guard: 'node --test --test-name-pattern="card adapter maps interaction pause" '
|
||||
+ 'test/version-recovery-card.test.mjs',
|
||||
because: 'recent pointer, keyboard, touch and native more-info interaction must defer the '
|
||||
+ 'silent kiosk reload by the same shared pause contract (#462 AC8)',
|
||||
patches: [{
|
||||
|
||||
@@ -11,15 +11,17 @@ const controller = read('src/version-recovery.ts');
|
||||
const styles = read('src/styles/base.styles.ts');
|
||||
|
||||
const occurrences = (source, fragment) => source.split(fragment).length - 1;
|
||||
const matches = (source, pattern) => source.match(pattern)?.length || 0;
|
||||
|
||||
test('one host seam owns every full-card config/get request', () => {
|
||||
const direct = "callWS({ type: 'houseplan/config/get'";
|
||||
assert.equal(occurrences(adapter, direct), 1, 'the adapter is the only WS request owner');
|
||||
assert.equal(occurrences(card, direct), 0, 'card flows must use the authoritative seam');
|
||||
assert.equal(occurrences(editor, direct), 0, 'lazy editor flows must use the host seam');
|
||||
assert.equal(occurrences(onboarding, direct), 0, 'onboarding flows must use the host seam');
|
||||
const direct = /callWS(?:<[^>]+>)?\(\{ type: 'houseplan\/config\/get'/g;
|
||||
assert.equal(matches(adapter, direct), 1, 'the adapter is the only WS request owner');
|
||||
assert.equal(matches(card, direct), 0, 'card flows must use the authoritative seam');
|
||||
assert.equal(matches(editor, direct), 0, 'lazy editor flows must use the host seam');
|
||||
assert.equal(matches(onboarding, direct), 0, 'onboarding flows must use the host seam');
|
||||
|
||||
assert.match(card, /private _getAuthoritativeConfig\(\): Promise<any>/);
|
||||
assert.match(card,
|
||||
/private _getAuthoritativeConfig\(\): Promise<AuthoritativeConfigResponse>/);
|
||||
assert.equal(occurrences(card, 'this._getAuthoritativeConfig()'), 2,
|
||||
'initial load and config-only reload both use the seam');
|
||||
assert.equal(occurrences(editor, 'this.host._getAuthoritativeConfig()'), 3,
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { cardVersionReloadSafetySnapshot } from '../test-build/version-recovery-card.js';
|
||||
|
||||
const pending = (size = 0) => ({ size });
|
||||
const debounce = (value = false) => ({ pending: () => value });
|
||||
|
||||
function safeHost(overrides = {}) {
|
||||
return {
|
||||
ownerDocument: { defaultView: null },
|
||||
isConnected: true,
|
||||
_config: { kiosk: true },
|
||||
_mode: 'view',
|
||||
_editing: false,
|
||||
_loadOk: true,
|
||||
_loading: false,
|
||||
_continuityDataReady: true,
|
||||
_resumeSettling: false,
|
||||
_connectionWasLost: false,
|
||||
_booting: false,
|
||||
_bootFading: false,
|
||||
_bootSettling: false,
|
||||
_bootSoft: false,
|
||||
_continuity: {
|
||||
hasCompleteFrame: true,
|
||||
state: 'steady',
|
||||
overlayBlocksInteraction: false,
|
||||
},
|
||||
_stageEl: { clientWidth: 800, clientHeight: 600 },
|
||||
_pointers: pending(),
|
||||
_touchContacts: pending(),
|
||||
_touchSequenceMultitouch: false,
|
||||
_roomPointer: null,
|
||||
_panStart: null,
|
||||
_panLock: null,
|
||||
_pinchStart: null,
|
||||
_swipeStart: null,
|
||||
_tabDrag: null,
|
||||
_tabDragRelease: null,
|
||||
_drag: null,
|
||||
_deviceDrag: null,
|
||||
_rlResize: null,
|
||||
_resize: null,
|
||||
_physicalDrag: null,
|
||||
_physicalRotate: null,
|
||||
_opDrag: null,
|
||||
_decorDraft: null,
|
||||
_decorMove: null,
|
||||
_dtDrag: null,
|
||||
_bdDrag: null,
|
||||
_furnTouchPending: null,
|
||||
_compassDrag: null,
|
||||
_viewportGestureDirty: false,
|
||||
_devicePositionBusy: false,
|
||||
_modeTransitionBusy: false,
|
||||
_cameraTransition: { active: false },
|
||||
_slide: '',
|
||||
_warmModeRequest: 0,
|
||||
_writesPending: 0,
|
||||
_saveConfigDebounced: debounce(),
|
||||
_pendingPhysicalWrites: pending(),
|
||||
_persistLayout: debounce(),
|
||||
_dirtyPos: pending(),
|
||||
_sentPos: pending(),
|
||||
_cyclePausedUntil: 0,
|
||||
_zoom: 1,
|
||||
_editorSecondaryDialogBlocked: false,
|
||||
_partitionDeleteDialog: null,
|
||||
_roomDeleteDialog: null,
|
||||
_backdropGuard: null,
|
||||
_vacFit: null,
|
||||
_editorSecondary: null,
|
||||
_furnPalette: null,
|
||||
_decorImagePalette: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test('card adapter baseline is safe only after the full rendered card settles', () => {
|
||||
assert.deepEqual(cardVersionReloadSafetySnapshot(safeHost()), {
|
||||
connected: true,
|
||||
initialFrameSettled: true,
|
||||
viewOnly: true,
|
||||
surfacesIdle: true,
|
||||
configWritesIdle: true,
|
||||
physicalWritesIdle: true,
|
||||
layoutWritesIdle: true,
|
||||
gesturesIdle: true,
|
||||
interactionPauseElapsed: true,
|
||||
baseZoom: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('card adapter maps editor state into the kiosk reload guard', () => {
|
||||
for (const overrides of [
|
||||
{ _config: { kiosk: false } },
|
||||
{ _mode: 'plan' },
|
||||
{ _editing: true },
|
||||
]) {
|
||||
assert.equal(cardVersionReloadSafetySnapshot(safeHost(overrides)).viewOnly, false);
|
||||
}
|
||||
});
|
||||
|
||||
test('card adapter maps blocking surfaces into the kiosk reload guard', () => {
|
||||
for (const overrides of [
|
||||
{ _editorSecondaryDialogBlocked: true },
|
||||
{ _partitionDeleteDialog: {} },
|
||||
{ _roomDeleteDialog: {} },
|
||||
{ _backdropGuard: {} },
|
||||
{ _vacFit: {} },
|
||||
{ _editorSecondary: { hasOpenGroup: true } },
|
||||
{ _furnPalette: {} },
|
||||
{ _decorImagePalette: {} },
|
||||
]) {
|
||||
assert.equal(cardVersionReloadSafetySnapshot(safeHost(overrides)).surfacesIdle, false);
|
||||
}
|
||||
});
|
||||
|
||||
test('card adapter maps config writes into the kiosk reload guard', () => {
|
||||
assert.equal(cardVersionReloadSafetySnapshot(safeHost({ _writesPending: 1 })).configWritesIdle,
|
||||
false);
|
||||
assert.equal(cardVersionReloadSafetySnapshot(safeHost({
|
||||
_saveConfigDebounced: debounce(true),
|
||||
})).configWritesIdle, false);
|
||||
});
|
||||
|
||||
test('card adapter maps interaction pause into the kiosk reload guard', () => {
|
||||
assert.equal(cardVersionReloadSafetySnapshot(safeHost({
|
||||
_cyclePausedUntil: Date.now() + 60_000,
|
||||
})).interactionPauseElapsed, false);
|
||||
});
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
"src/space-geometry.ts", "src/junction-limits.ts",
|
||||
"src/space-order.ts", "src/card-editor-validation.ts",
|
||||
"src/signing.ts", "src/initial-load.ts", "src/space-model-selection.ts", "src/space-dialog.ts",
|
||||
"src/visual-continuity.ts", "src/version-recovery.ts", "src/mode-transition.ts", "src/viewport-transition.ts", "src/room-fit.ts", "src/editor-runtime-loader.ts", "src/editor-secondary.ts", "src/pointer-modality.ts",
|
||||
"src/visual-continuity.ts", "src/version-recovery.ts", "src/version-recovery-card.ts", "src/mode-transition.ts", "src/viewport-transition.ts", "src/room-fit.ts", "src/editor-runtime-loader.ts", "src/editor-secondary.ts", "src/pointer-modality.ts",
|
||||
"src/render-device-snapshot.ts", "src/render-invalidation.ts", "src/houseplan-render-lifecycle.ts", "src/live-interaction-runtime.ts", "src/live-viewport.ts", "src/live-hover.ts", "src/live-editor.ts", "src/pointer-move-queue.ts",
|
||||
"src/command-stack.ts", "src/device-position-history.ts",
|
||||
"src/coordinate-canonicalization.ts",
|
||||
|
||||
Reference in New Issue
Block a user