feedback7 R22-E2EFIX: an Esc pressed the moment the 自选 picker opens closes it (its listener comes with the picker's commit); golden: unchanged
The 0.2.2 full browser pass failed diy.e2e 「5阶 / 6阶 slots: Esc cancels the picker…」 (GitHub #284's test) at the wait for the picker to close after Esc. The picker does open on a real click (the suite's other three tests open it with real mouse clicks; all green). The cause is a race in the UI: DiyPicker attached its keydown listener in a plain effect, which runs a frame after the picker is in the DOM, while the 干员调配 overlay's own Esc handler skips Esc as soon as `.diy-pick` exists — an Esc pressed in between was swallowed and the picker stayed open. Reproduced every time by dispatching Esc right after the picker renders; on a loaded machine the test's own Esc hit the window. Now a layout effect: the listener is live in the picker's first commit. - Test: diy.e2e (the Esc test) adds that immediate Esc — it fails on the parent with the full pass's error and passes now; the whole suite 4/4 (SP_E2E=1), test/ui unit tests 699 pass.
This commit is contained in:
1 parent
925f2d836d
commit
b972c1fc9e
2 files changed
+18
-3
No files matched your search
@@ -9,7 +9,7 @@
|
|||||||
// the model is ui/diyModel.js. Styles: css/screens/loadout.css (diy-*). An owned pick's card carries its operator's 潜能 /
|
// the model is ui/diyModel.js. Styles: css/screens/loadout.css (diy-*). An owned pick's card carries its operator's 潜能 /
|
||||||
// 练度 selects (0.2.2, screens/cultivation.js — the same per-operator settings as 干员调配, `ops`); a prototype has neither.
|
// 练度 selects (0.2.2, screens/cultivation.js — the same per-operator settings as 干员调配, `ops`); a prototype has neither.
|
||||||
|
|
||||||
import { useEffect, useState } from '../../vendor/hooks.module.js';
|
import { useLayoutEffect, useState } from '../../vendor/hooks.module.js';
|
||||||
import { html, Icon, Button, TierChip } from '../ui/components.js';
|
import { html, Icon, Button, TierChip } from '../ui/components.js';
|
||||||
import { Img, RichText, BondGlyph } from '../ui/gameComponents.js';
|
import { Img, RichText, BondGlyph } from '../ui/gameComponents.js';
|
||||||
import { chessAvatarUrl, chessPortraitUrl, profIconUrl, skillRecordIconUrl, moduleTypeIconUrl } from '../ui/assetUrls.js';
|
import { chessAvatarUrl, chessPortraitUrl, profIconUrl, skillRecordIconUrl, moduleTypeIconUrl } from '../ui/assetUrls.js';
|
||||||
@@ -146,8 +146,10 @@ export function DiyPicker(props) {
|
|||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
const [draft, setDraft] = useState(cur ? { ...cur } : null);
|
const [draft, setDraft] = useState(cur ? { ...cur } : null);
|
||||||
// Esc cancels only the picker, like its 取消 (GitHub #284, idea from PR #286); the 干员调配 overlay's own Esc skips
|
// Esc cancels only the picker, like its 取消 (GitHub #284, idea from PR #286); the 干员调配 overlay's own Esc skips
|
||||||
// while a picker is open, and a dialog over the picker (导入) still takes Esc first
|
// while a picker is open, and a dialog over the picker (导入) still takes Esc first. A layout effect: the listener is
|
||||||
useEffect(() => {
|
// attached in the same commit as the picker (a plain effect waits for the next frame, and an Esc pressed in between
|
||||||
|
// was swallowed — the overlay skipped it, the picker did not hear it yet; the 0.2.2 full browser pass)
|
||||||
|
useLayoutEffect(() => {
|
||||||
const onKey = (e) => {
|
const onKey = (e) => {
|
||||||
if (e.key !== 'Escape' || e.ctrlKey || e.metaKey || e.altKey || document.querySelector('.modal')) return;
|
if (e.key !== 'Escape' || e.ctrlKey || e.metaKey || e.altKey || document.querySelector('.modal')) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -134,6 +134,19 @@ describe('自选编队 picker: Esc closes only the picker (real server, no art n
|
|||||||
assert.ok(await c.page.$('.lo'), `${slot}: Esc keeps the 干员调配 overlay${search ? ' (from the search field)' : ''}`);
|
assert.ok(await c.page.$('.lo'), `${slot}: Esc keeps the 干员调配 overlay${search ? ' (from the search field)' : ''}`);
|
||||||
assert.equal((await c.page.$$('.diy-slot')).length, 4, `${slot}: the four slots stay`);
|
assert.equal((await c.page.$$('.diy-slot')).length, 4, `${slot}: the four slots stay`);
|
||||||
}
|
}
|
||||||
|
// an Esc pressed the moment the picker appears — before the next animation frame — closes it too: its listener comes
|
||||||
|
// with the picker's own commit (a plain effect attached it a frame later and the overlay skipped the key: the
|
||||||
|
// picker stayed open — the 0.2.2 full browser pass)
|
||||||
|
const opened = await c.page.evaluate(async (slot, sel) => {
|
||||||
|
document.querySelector(`.diy-slot[data-slot="${slot}"] .diy-slot__fill`).click();
|
||||||
|
for (let k = 0; k < 50 && !document.querySelector(sel); k++) await Promise.resolve();
|
||||||
|
const open = !!document.querySelector(sel);
|
||||||
|
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }));
|
||||||
|
return open;
|
||||||
|
}, SLOT, picker);
|
||||||
|
assert.ok(opened, 'the picker opened');
|
||||||
|
await c.page.waitForSelector(picker, { hidden: true, timeout: 3000 });
|
||||||
|
assert.ok(await c.page.$('.lo'), 'an immediate Esc keeps the 干员调配 overlay');
|
||||||
assert.equal(await saved(), before, 'cancelling a picker does not change the saved picks');
|
assert.equal(await saved(), before, 'cancelling a picker does not change the saved picks');
|
||||||
await c.page.keyboard.press('Escape');
|
await c.page.keyboard.press('Escape');
|
||||||
await c.page.waitForFunction(() => !document.querySelector('.lo'), { timeout: 3000 });
|
await c.page.waitForFunction(() => !document.querySelector('.lo'), { timeout: 3000 });
|
||||||
|
|||||||
Reference in new issue
Block a user