feedback6: merge fb6-diy-esc — Esc in the 自选 picker closes only the picker (GitHub #284 by @Xinglan233, idea from PR #286)

- Golden unchanged (UI only); broad node --test 5147 / 5147; lint 0 errors; the new browser test 1 / 1 on the branch.
This commit is contained in:
sganggs committed 2026-10-07 18:54:12 +08:00
commit de0b830465
3 files changed
+56 -2

No files matched your search

+13 -1
View File
@@ -8,7 +8,7 @@
// /diy.js). Out of match: the next match takes the picks. The picks live in ui/loadoutSync.js (localStorage + room.diy);
// the model is ui/diyModel.js. Styles: css/screens/loadout.css (diy-*).
import { useState } from '../../vendor/hooks.module.js';
import { useEffect, useState } from '../../vendor/hooks.module.js';
import { html, Icon, Button, TierChip } from '../ui/components.js';
import { Img, RichText, BondGlyph } from '../ui/gameComponents.js';
import { chessAvatarUrl, chessPortraitUrl, profIconUrl, skillRecordIconUrl, moduleTypeIconUrl } from '../ui/assetUrls.js';
@@ -141,6 +141,18 @@ export function DiyPicker(props) {
const [filter, setFilter] = useState('all');
const [query, setQuery] = useState('');
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
// while a picker is open, and a dialog over the picker (导入) still takes Esc first
useEffect(() => {
const onKey = (e) => {
if (e.key !== 'Escape' || e.ctrlKey || e.metaKey || e.altKey || document.querySelector('.modal')) return;
e.preventDefault();
e.stopImmediatePropagation();
props.onClose();
};
window.addEventListener('keydown', onKey, true);
return () => window.removeEventListener('keydown', onKey, true);
}, [props.onClose]);
return DiyPickerView({ ...props, filter, query, draft, onFilter: setFilter, onQuery: setQuery, onDraft: setDraft });
}
+1
View File
@@ -550,6 +550,7 @@ function LoadoutScreen({ st }) {
const onKey = (e) => {
if (e.ctrlKey || e.metaKey || e.altKey) return;
if (document.querySelector('.modal')) return; // a confirm dialog handles its own keys
if (e.key === 'Escape' && document.querySelector('.diy-pick')) return; // the 自选 picker's Esc closes only the picker (diy.js)
const typing = e.target && /^(INPUT|SELECT|TEXTAREA)$/.test(e.target.tagName);
if (e.key === 'Escape') { e.preventDefault(); e.stopImmediatePropagation(); closeLoadout(); return; }
if (typing) return;
+42 -1
View File
@@ -17,11 +17,52 @@ import { existsSync } from 'node:fs';
import path from 'node:path';
import { Client, ROOT, sleep, hasChrome, startRealServer, problemsOf } from '../e2e/client.mjs';
const ENABLED = process.env.SP_E2E === '1' && hasChrome() && existsSync(path.join(ROOT, 'public/assets'));
const UI_ENABLED = process.env.SP_E2E === '1' && hasChrome();
const ENABLED = UI_ENABLED && existsSync(path.join(ROOT, 'public/assets'));
const SLOT = 'chess_char_5_diy1_a';
const SIEGE = 'char_112_siege';
const PICK = { charId: SIEGE, skillIndex: 2, uniEquipId: 'uniequip_002_siege' };
// GitHub #284 (idea from PR #286): Esc in the picker cancels only the picker, like its 取消, also from its search
// field — the 干员调配 overlay and its four slots stay, and the saved picks do not change; the next Esc closes the overlay
describe('自选编队 picker: Esc closes only the picker (real server, no art needed)', { skip: !UI_ENABLED && 'set SP_E2E=1 (and have Chrome)' }, () => {
test('5阶 / 6阶 slots: Esc cancels the picker, the overlay stays; a second Esc closes the overlay', { timeout: 90000 }, async () => {
const srv = await startRealServer();
const P = (await import('puppeteer-core')).default;
const c = new Client(P, srv.base, 'diy-esc', { prefix: 'diy-esc' });
const picker = '[data-testid="diy-picker"]';
const saved = () => c.page.evaluate(() => localStorage.getItem('sp.pref.diy'));
try {
await c.open();
await c.enter('自选取消');
await c.click('.lobby-screen [data-testid="loadout-open"]');
await c.page.waitForSelector('.lo .lo-tab[data-tab="diy"]', { visible: true, timeout: 15000 });
await c.click('.lo .lo-tab[data-tab="diy"]');
const before = await saved();
for (const [slot, search] of [[SLOT, false], ['chess_char_6_diy1_a', true]]) {
await c.page.waitForSelector(`.diy-slot[data-slot="${slot}"] .diy-slot__fill`, { visible: true, timeout: 15000 });
await c.click(`.diy-slot[data-slot="${slot}"] .diy-slot__fill`);
await c.page.waitForSelector(picker, { visible: true, timeout: 8000 });
if (search) {
await c.click(`${picker} input[type="search"]`);
await c.page.keyboard.type('推进');
}
await c.page.keyboard.press('Escape');
await c.page.waitForSelector(picker, { hidden: true, timeout: 3000 });
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 saved(), before, 'cancelling a picker does not change the saved picks');
await c.page.keyboard.press('Escape');
await c.page.waitForFunction(() => !document.querySelector('.lo'), { timeout: 3000 });
assert.deepEqual(c.problems.filter((p) => p.startsWith('pageerror:')), []);
} finally {
await c.close();
await srv.stop();
}
});
});
describe('0.2.0 自选编队 — a slotted operator in the own shop and battle (real server)', { skip: !ENABLED && 'set SP_E2E=1 (Chrome + public/assets)' }, () => {
test('自选编队: slot 推进之王 → level-5 shop card (自选) → buy → deploy → the local battle fields 推进之王', { timeout: 6 * 60 * 1000 }, async () => {
const srv = await startRealServer({ fast: { timerScale: 0.5, combatSpeed: 2, startRound: 1, kit: 0, level: 5, shop: [SLOT] } });