feedback5: merge W5Z — the co-op briefing's top bar fits the third tab (自选编队) in zh and en at every width (the only product bug of the 0.2.0 full pass); three tests hardened against load (client-wait counts calls, leftovers waits for the item to fly home, bossfield checks 瘫痪 on copies with a golden 催泪瓦斯); golden unchanged

This commit is contained in:
sganggs committed 2026-10-06 22:29:39 +08:00
commit b9b647eecf
5 files changed
+116 -31

No files matched your search

+12 -1
View File
@@ -10,7 +10,8 @@
// 4. solo battle: the pause button sends g.pause {on}, m.public.paused shows the overlay and freezes the countdown,
// Space resumes; no pause control in co-op or in prep.
// Real server (test/e2e/fastServer.mjs, real clicks and canvas drags):
// 1+4. a solo run: equip two items on an operator, drop a third → dialog → 取消 (nothing sent) → pick the OLDER item →
// 1+4. a solo run: equip two items on an operator, drop a third → dialog → 取消 (nothing sent, the item drawn back on its
// bench slot) → pick the OLDER item →
// the real engine destroys exactly that one; then ready → the real local battle → pause (the battle clock and the
// countdown stand still) → 继续作战;
// 3. the server restarts while the briefing is on screen → 「服务器会话已重置,上一局模拟已结束」 and the lobby.
@@ -275,6 +276,7 @@ describe('client leftovers — real server', { skip: !ENABLED && 'set SP_E2E=1 (
const equipped = await itemsOf();
assert.deepEqual(equipped.map((x) => x.id), KIT_ITEMS.slice(0, 2));
const third = await handItem(KIT_ITEMS[2]);
const benchSlot = await c.piecePoint(third.uid, 0.5); // where the view draws it in the hand
// the third: dialog → 取消 → nothing sent / changed
await dropItem(third);
@@ -289,6 +291,15 @@ describe('client leftovers — real server', { skip: !ENABLED && 'set SP_E2E=1 (
assert.equal((await c.requests('g.equip')).length, sentBefore, '取消: no g.equip');
assert.deepEqual(await itemsOf(), equipped, '取消: unchanged');
assert.ok(await handItem(KIT_ITEMS[2]), '取消: the item stays in the hand');
// ... and is drawn back on its bench slot before it is picked up again. The view keeps a dropped piece on the drop
// tile for 1.3 s (render/app/tune.js DROP_PENDING_MS), then flies it back in a frame-timed tween: with few frames
// (software-rendered headless Chrome on a loaded machine) the drag aimed where it stood a moment before grabbed the
// operator under it, whose direction wheel then swallowed the retry (the 0.2.0 candidate's full pass).
const back = await c.page.waitForFunction((uid, x, y) => {
const r = globalThis.__SP_VIEW__?.pieceScreenRect(uid);
return !!r && Math.abs(r.left + r.width / 2 - x) < 2 && Math.abs(r.top + r.height / 2 - y) < 2;
}, { timeout: 15000, polling: 100 }, third.uid, benchSlot.x, benchSlot.y).then(() => true, () => false);
assert.ok(back, '取消: the item is drawn back on its bench slot');
// again → pick the OLDER item (not the server's default either way: explicit replaceUid) → 确认替换
await dropItem(third);
+26 -12
View File
@@ -243,19 +243,33 @@ describe('干员调配 overlay (real server, headless Chrome)', { skip: !ENABLED
await page.waitForSelector('.lo-top .lo-deadline', { visible: true, timeout: 3000 });
assert.match(await page.$eval('.lo-top .lo-deadline', (el) => el.getAttribute('aria-label')), /剩余\d+秒/);
// review: the two new 导出 / 导入 buttons must fit next to the countdown on a narrow phone in landscape (667×375).
// 0.2.0: the third tab (自选编队) ran the right column into the tabs there, at 844×390, and in English at every size.
// Only the size changes (toggling isMobile / hasTouch would make puppeteer reload the page).
await page.setViewport({ width: 667, height: 375 });
const bar = await page.evaluate(() => {
const top = document.querySelector('.lo-top');
const right = document.querySelector('.lo-top__right').getBoundingClientRect();
const center = document.querySelector('.lo-top__center').getBoundingClientRect();
const buttons = [...document.querySelectorAll('.lo-top__right .btn')].map((b) => Math.round(b.getBoundingClientRect().right));
return { overflow: top.scrollWidth - top.clientWidth, overlap: right.left < center.right, offscreen: right.right > innerWidth, buttons };
});
assert.ok(bar.overflow <= 0, `the top bar does not scroll sideways (${JSON.stringify(bar)})`);
assert.equal(bar.overlap, false, `the right column does not run into the title (${JSON.stringify(bar)})`);
assert.equal(bar.offscreen, false, `every button ends inside the viewport (${JSON.stringify(bar)})`);
await page.screenshot({ path: path.join(OUT, 'loadout-narrow-toolbar.png') });
const switchLang = async (lang) => {
await page.evaluate((l) => import('/js/ui/lang.js').then((m) => m.switchLang(l)), lang);
await page.waitForFunction((l) => document.documentElement.lang.startsWith(l) && /[一-鿿]/.test(document.querySelector('.lo-tabs').textContent) === (l === 'zh'), { timeout: 5000 }, lang);
};
for (const [lang, sizes] of [['zh', [[667, 375], [844, 390]]], ['en', [[667, 375], [844, 390], [1920, 1080]]]]) {
if (lang !== 'zh') await switchLang(lang);
for (const [w, h] of sizes) {
await page.setViewport({ width: w, height: h });
const bar = await page.evaluate(() => {
const top = document.querySelector('.lo-top');
const tabs = document.querySelector('.lo-tabs');
const right = document.querySelector('.lo-top__right').getBoundingClientRect();
const center = document.querySelector('.lo-top__center').getBoundingClientRect();
const buttons = [...document.querySelectorAll('.lo-top__right .btn')].map((b) => Math.round(b.getBoundingClientRect().right));
return { overflow: top.scrollWidth - top.clientWidth, overlap: right.left < center.right, offscreen: right.right > innerWidth, tabsCut: tabs.scrollWidth - tabs.clientWidth, buttons };
});
const at = `${lang} ${w}×${h} ${JSON.stringify(bar)}`;
assert.ok(bar.overflow <= 0, `the top bar does not scroll sideways (${at})`);
assert.equal(bar.overlap, false, `the right column does not run into the title (${at})`);
assert.equal(bar.offscreen, false, `every button ends inside the viewport (${at})`);
assert.ok(bar.tabsCut <= 0, `the three tabs show in full (${at})`);
if (w === 667) await page.screenshot({ path: path.join(OUT, `loadout-narrow-toolbar${lang === 'zh' ? '' : `-${lang}`}.png`) });
}
}
await switchLang('zh');
await page.setViewport({ width: 1920, height: 1080 });
await page.type('.lo-search input', '隐现');
await page.waitForFunction(() => document.querySelectorAll('.lo-card').length === 1, { timeout: 5000 });