Ctrl K
ComunidadGuías…

frontend/e2e/canonical-routes.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 } from 'playwright-core';
import { launch, settle, visit, VIEWPORTS, requiredEnv } from './harness.ts';
import { ITEM, openItemPage } from './item-fixture.ts';
import { DETAIL, openFollowFeed } from './follow-fixture.ts';
import { openPanel } from './settings-fixture.ts';
import { JAV_DETAIL, mockJavDetail } from './jav-detail-fixture.ts';

describe('规范路由与设置历史', () => {
  let browser: Browser;
  before(async () => { browser = await launch() });
  after(async () => { await browser.close() });
  for (const viewport of VIEWPORTS) {
    it(`${viewport.name} 关注添加页签与空状态站内链接保留当前文档`, { timeout: 60000 }, async () => {
      const opened = await visit(browser, '/subscriptions', viewport, async page => {
        await page.route('**/api/follow?summary=1', route => route.fulfill({ json: {
          sources: [], counts: {}, author_aliases: [], alias_suggestions: [], suggestions: [],
        } }));
        await page.route('**/api/follow/credentials', route => route.fulfill({ json: { providers: [] } }));
        await page.route('**/api/feeds', route => route.fulfill({ json: { sources: [], unread: 0 } }));
        await page.route('**/api/stats', async route => {
          const report = await (await route.fetch()).json();
          await route.fulfill({ json: { ...report, by_loc: [] } });
        });
      });
      const { page } = opened;
      try {
        const add = page.getByRole('button', { name: '添加关注', exact: true });
        await add.waitFor();
        const timeOrigin = await page.evaluate(() => performance.timeOrigin);
        const documents: string[] = [];
        page.on('request', request => { if (request.isNavigationRequest()) documents.push(request.url()) });
        await page.evaluate(() => Object.assign(window, { followOverview: document.querySelector('[aria-label="关注概览"]') }));
        await add.click();
        await page.getByRole('textbox', { name: '来源链接、名字或 id', exact: true }).waitFor();
        assert.equal(new URL(page.url()).searchParams.get('tab'), 'add');
        assert.equal(await page.evaluate(() => (window as unknown as { followOverview: Element }).followOverview
          === document.querySelector('[aria-label="关注概览"]')), true, '页签切换保留页面宿主');
        await page.getByRole('tab', { name: '关注列表', exact: true }).click();
        await add.waitFor();
        await page.getByRole('button', { name: '统计', exact: true }).click();
        await page.getByRole('link', { name: '添加关注', exact: true }).click();
        await page.getByRole('textbox', { name: '来源链接、名字或 id', exact: true }).waitFor();
        await page.goBack();
        await page.getByRole('link', { name: '添加关注', exact: true }).waitFor();
        assert.equal(new URL(page.url()).pathname, '/stats');
        await page.goForward();
        await page.getByRole('textbox', { name: '来源链接、名字或 id', exact: true }).waitFor();
        assert.equal(await page.evaluate(() => performance.timeOrigin), timeOrigin);
        assert.deepEqual(documents, [], '页签、站内链接与历史切换均不请求新文档');
        assert.deepEqual(opened.problems, []);
      } finally { await opened.close() }
    });

    it(`${viewport.name} SPA 作品导航采用接口规范番号并保留历史上下文`, { timeout: 60000 }, async () => {
      const opened = await visit(browser, '/', viewport);
      const { page } = opened;
      try {
        await mockJavDetail(page);
        let details = 0;
        await page.route(/\/api\/jav\/detail\?/, route => {
          details++;
          return route.fulfill({ json: JAV_DETAIL });
        });
        const depth = await page.evaluate(() => history.length);
        await page.evaluate(async () => {
          const entry = '/dist/peach-app.js';
          (await import(entry)).peachHistory.push('/works/exm001?t=42#sample');
        });
        await page.waitForURL('**/works/EXM-001?t=42#sample');
        await page.locator('[data-jav-info] h1').waitFor();
        await settle(page, '#stage');
        assert.equal(details, 1);
        assert.equal(await page.evaluate(() => history.length), depth + 1);
        assert.deepEqual(opened.problems, []);
      } finally { await opened.close() }
    });
    it(`${viewport.name} 本地标签 ID 深链可展示聚合并刷新`, { timeout: 60000 }, async () => {
      const opened = await visit(browser, '/', viewport);
      const { page } = opened;
      try {
        const seeded = await page.request.post(new URL('/api/item-tag', page.url()).href, { data: {
          id: Number(requiredEnv('PEACH_E2E_ITEM')), operation: 'add', tag: '巨乳',
        } });
        assert.equal(seeded.status(), 200, '临时演示库创建标签身份');
        const response = await page.request.get(new URL('/api/index?kind=tags&q=巨乳&limit=1', page.url()).href);
        const data = await response.json() as { items: { entity_id: number; k: string }[] };
        const tag = data.items[0]!;
        assert.ok(tag?.entity_id > 0);
        await page.goto(new URL(`/tags/${tag.entity_id}`, page.url()).href);
        const title = page.locator('#index [data-entity-title] h2');
        await title.waitFor();
        await settle(page);
        assert.equal(await title.innerText(), tag.k);
        assert.equal(await page.locator('#index [data-namepick-toggle]').count(), 0);
        await page.reload();
        await title.waitFor();
        await settle(page);
        assert.equal(await title.innerText(), tag.k);
        assert.equal(new URL(page.url()).pathname, `/tags/${tag.entity_id}`);
        assert.deepEqual(opened.problems, []);
      } finally { await opened.close() }
    });
    it(`${viewport.name} 保存的来源记录使用关注条目的规范地址`, { timeout: 60000 }, async () => {
      const opened = await openFollowFeed(browser, '/follow', viewport);
      const { page } = opened;
      try {
        await page.route(url => url.pathname === '/api/item', route => route.fulfill({ json: {
          id: 70010, location: 'online', medium: 'video', follow_item_id: DETAIL.media,
        } }));
        const depth = await page.evaluate(() => history.length);
        await page.evaluate(async () => {
          const entry = '/dist/peach-app.js';
          (await import(entry)).peachHistory.push('/assets/70010');
        });
        await page.waitForURL(`**/posts/${DETAIL.media}`);
        await page.locator('#stage [data-follow-detail]').waitFor();
        assert.equal(await page.evaluate(() => history.length), depth + 1);
        await page.goBack();
        await page.waitForURL(url => url.pathname === '/follow');
        await page.locator('[data-follow-list] > [data-follow-item]').first().waitFor();
      } finally { await opened.close() }
    });
    it(`${viewport.name} 设置分区返回同一份列表`, { timeout: 60000 }, async () => {
      const opened = await visit(browser, '/', viewport);
      const { page } = opened;
      try {
        await page.locator('#grid [data-media-card]').first().waitFor();
        await page.evaluate(() => Object.assign(window, { routeGrid: document.querySelector('#grid .peach-react') }));
        await openPanel(page);
        await page.waitForURL('**/settings/appearance');
        const panel = page.locator('#settingsPanel');
        await panel.getByRole('tab', { name: '浏览与播放', exact: true }).click();
        await page.waitForURL('**/settings/playback');
        await panel.getByRole('button', { name: '关闭设置' }).click();
        await page.waitForURL(url => url.pathname === '/');
        await panel.waitFor({ state: 'hidden' });
        assert.ok(await page.evaluate(() => (window as unknown as { routeGrid: Element }).routeGrid === document.querySelector('#grid .peach-react')));
        await page.goForward();
        await page.waitForURL('**/settings/playback');
        await panel.getByRole('heading', { name: '浏览与播放', exact: true }).waitFor();
        assert.deepEqual(opened.problems, []);
      } finally { await opened.close() }
    });
    it(`${viewport.name} 设置覆盖播放详情保留播放器实例`, { timeout: 60000 }, async () => {
      const opened = await openItemPage(browser, `/assets/${ITEM.plain}`, viewport);
      const { page } = opened;
      try {
        await page.locator('#stage video.vjs-tech').waitFor({ state: 'attached' });
        await page.evaluate(() => {
          Object.assign(window, { routeVideo: document.querySelector('#stage video') });
          document.querySelector<HTMLElement>('#settingsBtn')!.click();
        });
        await page.waitForURL('**/settings/appearance');
        await page.locator('#settingsClose').dispatchEvent('click');
        await page.waitForURL(`**/assets/${ITEM.plain}`);
        await page.locator('#settingsPanel').waitFor({ state: 'hidden' });
        await settle(page);
        assert.ok(await page.evaluate(() => (window as unknown as { routeVideo: Element }).routeVideo === document.querySelector('#stage video')));
        assert.ok(await page.locator('#stage').evaluate(node => (node as HTMLDialogElement).open));
      } finally { await opened.close() }
    });
  }
});