Ctrl K
ComunidadGuías…

frontend/e2e/horizontal-navigation.test.ts

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/* 横向导航的溢出提示:方向跟随滚动位置,容器伸缩后重算,原生滚动条不占位。 */
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() }
  });
});