/** 回归缺口:异步列表替换骨架时,已显示的相邻卡片不应因同样五行内容而上下跳动。 */ import assert from "node:assert/strict"; import { test } from "node:test"; import { backendOrigin, login, scenario } from "./helpers/console-browser.mjs"; for (const width of [1440, 390]) { test( `home list reserves its rendered row geometry at ${width}px`, { timeout: 30000 }, (t) => scenario(t, {}, async (page) => { await page.setViewportSize({ width, height: 900 }); await page.emulateMedia({ reducedMotion: "reduce" }); let release; const held = new Promise((resolve) => { release = resolve; }); await page.route(`${backendOrigin}/console/v1/**`, async (route) => { const path = new URL(route.request().url()).pathname; if (![/\/ticket$/, /\/announcement$/].some((end) => end.test(path))) return route.fallback(); await held; return route.fulfill({ json: { status: 200, msg: "", data: { count: 5, list: Array.from({ length: 5 }, (_, index) => ({ id: index + 1, title: `本地布局验收 ${index + 1}`, create_time: 1730000000, post_time: 1730000000, status: "待回复", color: "#0284c7", })), }, }, }); }); const geometry = () => page.locator(".home-tickets, .home-notices").evaluateAll((cards) => cards.map((card) => { const box = card.getBoundingClientRect(); return { card: card.classList.contains("home-tickets") ? "tickets" : "notices", top: box.top + scrollY, height: box.height, }; }), ); let before; try { await login(page, "/"); await page .locator(".home-tickets .home-row-skeleton") .first() .waitFor(); await page.evaluate(() => document.fonts.ready); // 只比较本次控制的两个响应,避免混入其他独立面板的正常数据替换。 await page.waitForFunction( () => !document.querySelector( ".home-servers .home-row-skeleton, .home-expiring .home-row-skeleton, .home-referral .p-skeleton", ), ); before = await geometry(); } finally { release(); } await page.locator(".home-notices .notice-row").last().waitFor(); await page.locator(".home-tickets .home-row").last().waitFor(); const after = await geometry(); for (let index = 0; index < before.length; index++) { for (const dimension of ["height", "top"]) assert.ok( Math.abs(before[index][dimension] - after[index][dimension]) <= 1, `${before[index].card} ${dimension} changed: ${before[index][dimension]} -> ${after[index][dimension]}`, ); } return { width, sameRowGeometryAfterData: true }; }), ); }