frontend/e2e/horizontal-navigation.test.ts
Rama master · réplica de lectura del repositorio original.
/* 横向导航的溢出提示:方向跟随滚动位置,容器伸缩后重算,原生滚动条不占位。 */
import assert from 'node:assert/strict';
import { after, before, describe, it } from 'node:test';
import type { Browser, Locator } from 'playwright-core';
import { launch, settle, visit, VIEWPORTS } from './harness.ts';
import { openFollowFeed } from './follow-fixture.ts';
const MOBILE = VIEWPORTS.find(viewport => viewport.mobile)!;
async function edges(row: Locator, left: boolean, right: boolean) {
await row.page().waitForFunction(([selector, expectedLeft, expectedRight]) => {
const node = document.querySelector<HTMLElement>(selector);
return node?.dataset.overflowLeft === String(expectedLeft)
&& node?.dataset.overflowRight === String(expectedRight);
}, [await row.evaluate(node => {
node.setAttribute('data-scroll-test', 'active');
return '[data-scroll-test="active"]';
}), left, right], { timeout: 5_000 });
const style = await row.evaluate(node => ({
bar: getComputedStyle(node).scrollbarWidth,
mask: getComputedStyle(node).maskImage,
}));
assert.equal(style.bar, 'none', '横向导航露出了原生滚动条');
assert.equal(style.mask.includes('linear-gradient'), left || right, '边缘提示与可滚动方向不一致');
await row.evaluate(node => node.removeAttribute('data-scroll-test'));
}
async function scrollPositions(row: Locator) {
const range = await row.evaluate(node => node.scrollWidth - node.clientWidth);
assert.ok(range > 10, `夹具没有产生横向溢出:${range}`);
await row.evaluate(node => { node.scrollLeft = 0 });
await edges(row, false, true);
await row.evaluate(node => { node.scrollLeft = (node.scrollWidth - node.clientWidth) / 2 });
await edges(row, true, true);
await row.evaluate(node => { node.scrollLeft = node.scrollWidth });
await edges(row, true, false);
}
describe('横向导航提示', () => {
let browser: Browser;
before(async () => { browser = await launch() });
after(async () => { await browser?.close() });
it('复核分类在窄屏横滚,桌面分类列不保留横滚提示', { timeout: 60_000 }, async () => {
const opened = await visit(browser, '/review', MOBILE);
try {
const row = opened.page.getByRole('tablist', { name: '复核分类' });
await row.waitFor();
await settle(opened.page);
await scrollPositions(row);
await opened.page.setViewportSize({ width: 1280, height: 844 });
await opened.page.waitForFunction(() => {
const node = document.querySelector('[role="tablist"][aria-label="复核分类"]');
return node?.getAttribute('aria-orientation') === 'vertical';
});
assert.equal(await row.evaluate(node => getComputedStyle(node).maskImage), 'none');
await opened.page.setViewportSize({ width: 390, height: 844 });
await row.evaluate(node => { node.scrollLeft = 0 });
await edges(row, false, true);
await row.getByRole('tab').first().focus();
await opened.page.keyboard.press('End');
await edges(row, true, false);
const geometry = await row.evaluate(node => {
const selected = node.querySelector('[aria-selected="true"]')!.getBoundingClientRect();
const host = node.getBoundingClientRect();
return selected.left >= host.left - 1 && selected.right <= host.right + 1;
});
assert.ok(geometry, '键盘选中的分类没有进入可见范围');
assert.deepEqual(opened.problems, []);
} finally { await opened.close() }
});
it('标签类型和首字导航都显示左右提示,放宽后取消首字提示', { timeout: 60_000 }, async () => {
const opened = await visit(browser, '/tags', MOBILE, async page => {
await page.route('**/api/index?**', route => route.fulfill({ json: {
items: Array.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ', (letter, at) => ({ k: `${letter}标签`, n: at + 1, cat: 'general' })),
categories: { meta: 3, relationship: 3, role: 3, appearance: 3, scene: 3, story: 3, position: 3, general: 5 },
has_more: false,
} }));
});
try {
const page = opened.page;
const row = page.getByRole('group', { name: '标签类型' });
await row.waitFor();
await settle(page);
await scrollPositions(row);
const alphabet = page.getByRole('navigation', { name: '按首字跳转' });
await scrollPositions(alphabet);
await page.setViewportSize({ width: 1600, height: 900 });
await edges(alphabet, false, false);
await alphabet.getByRole('button').last().evaluate(node => { node.textContent = '很长的分类名称'.repeat(40) });
await edges(alphabet, false, true);
assert.deepEqual(opened.problems, []);
} finally { await opened.close() }
});
it('关注图片视图的下排操作支持横滚和边缘提示', { timeout: 60_000 }, async () => {
const opened = await openFollowFeed(browser, '/follow?media=images', MOBILE);
try {
await scrollPositions(opened.page.locator('[data-follow-filter-glass] > [data-filter-row="bottom"]'));
assert.deepEqual(opened.problems, []);
} finally { await opened.close() }
});
});