test(mutation): свидетели жестов и подсказки лестниц — без браузера (#676)

#659 закрыл реестр браузерных гвардов ровно на 200 и проверяет число
точно: два новых смок-свидетеля #676 подняли его до 202, и юниты на
ребейзнутом кандидате покраснели («browser guard inventory is reviewed,
capped and exact»).

Оба инварианта — проводка Lit-шаблонов, которую без браузера не
импортировать, но можно засвидетельствовать по исходнику, как #659 сделал
для прежних трёх лестничных мутантов: подавление синтезированного click
после жеста (`_suppressClick` на один тик в конце протяжки и жеста узла,
`@click=${stop}` на обоих видах узлов) и условие подсказки Просмотра
(`if (!active) return;` — тот же признак, что делает лестницу ссылкой).
Мутанты переведены в группу `unitGuard('test/stairs-box.test.mjs')`,
свидетель — новый тест в `mutation-browser-offload.test.mjs`; поведение в
Chromium по-прежнему держит `demo/smoke_stairs.mjs` (AC6, AC8).

Issue: #676
User-Visible: no
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
Claude
2026-09-27 17:54:21 +03:00
parent ab89187f4f
commit dd905888cb
2 changed files with 30 additions and 0 deletions
+6
View File
@@ -13149,6 +13149,12 @@ const UNIT_GUARD_GROUPS = [
'stairs-link-auto-creates-target-object',
'stairs-broken-targets-become-active',
]],
// #676: gesture-click suppression and the tooltip's `active` gate are template
// wiring; the box model beside them is pure and tested in stairs-box.test.mjs.
[unitGuard('test/stairs-box.test.mjs'), [
'stairs-gesture-click-reaches-plan-tool',
'stairs-view-tooltip-ignores-target-state',
]],
[unitGuard('test/device-hit-owner.test.mjs'), [
'device-hit-scroll-observer-disabled',
]],
+24
View File
@@ -150,3 +150,27 @@ test('#659 room gear drag keeps the click suppression window after a real move',
contains('src/room-gear-drag.ts',
'this.suppressClickUntil = performance.now() + 700;');
});
test('#676 stair gestures swallow the synthesized click and the View tooltip keeps the link condition', () => {
// Gesture → click suppression: the drag end (draft or handle) arms the host's
// `_suppressClick` for one tick, and every frame handle stops its own click.
// `demo/smoke_stairs.mjs` proves the behaviour in Chromium (AC6/AC8); this is
// the Node witness for the wiring that Lit templates cannot expose.
const editor = contains('src/stairs-editor.ts',
' this.owner._suppressClick = true;\n'
+ ' setTimeout(() => { this.owner._suppressClick = false; }, 0);\n',
' this.draft = null;\n this.swallowNextClick();\n',
' if (!drag || drag.pid !== event.pointerId) return false;\n this.swallowNextClick();\n',
"@pointerdown=${(event: PointerEvent) => this.pointerDown(event, stair, 'resize', { sx: handle.sx, sy: handle.sy })}\n"
+ ' @click=${stop}></circle>',
"@pointerdown=${(event: PointerEvent) => this.pointerDown(event, stair, 'rotate')}\n"
+ ' @click=${stop}></circle>',
);
assert.equal((editor.match(/@click=\$\{stop\}/g) || []).length, 2, 'both handle kinds stop their click');
// Tooltip only where the stair is a link: the same `active` that gates navigation.
const view = contains('src/stairs-view.ts',
' const tip = (event: PointerEvent): void => {\n if (!active) return;\n',
"this.owner._showTip(event, this.owner._t('stairs.tooltip_navigate', { title: targetTitle }), '');",
);
assert.doesNotMatch(view, /const tip = \(event: PointerEvent\): void => \{\n\s*this\.owner\._showTip/);
});