Ctrl K
ComunidadGuías…

frontend/e2e/library-sharing.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 { fileURLToPath } from 'node:url';
import type { Browser } from 'playwright-core';

import { launch, layout, settle, visit, VIEWPORTS } from './harness.ts';

describe('馆藏与口味分享', () => {
  let browser: Browser;
  before(async () => { browser = await launch() });
  after(async () => { await browser?.close() });
  for (const viewport of VIEWPORTS) {
    it(`${viewport.name}:连接圆点和文字使用对应语义色,浅色和暗色可辨识`, async () => {
      const opened = await visit(browser, '/settings/library', viewport, async page => {
        await page.route('**/api/configuration', async route => {
          const response = await route.fetch();
          await route.fulfill({ json: { ...await response.json(), editable: false,
            media_sources: [
              { location: 'local', root: 'R:\\Media', path: 'R:\\Media', online: false },
              { location: '115', root: 'B:\\', path: 'B:\\', online: true },
              { location: 'pikpak', root: 'A:\\', path: 'A:\\' },
            ] } });
        });
      });
      try {
        const status = opened.page.locator('[data-mount="offline"]').first();
        await status.waitFor();
        for (const theme of ['light', 'dark']) {
          await opened.page.evaluate(value => {
            document.documentElement.dataset.theme = value;
            document.documentElement.classList.toggle('dark', value === 'dark');
          }, theme);
          for (const [state, textToken, dotToken] of [
            ['offline', '--color-status-yellow-text', '--color-yellow-500'],
            ['online', theme === 'dark' ? '--color-green-400' : '--color-green-700', '--color-green-500'],
            ['unknown', '--color-text-secondary', '--color-text-tertiary'],
          ]) {
            const colors = await opened.page.locator(`[data-mount="${state}"]`).first().evaluate((element, tokens) => {
            const halo = element.firstElementChild!;
            const dot = halo.firstElementChild!;
            const probe = document.createElement('span');
            element.append(probe);
            probe.style.color = `var(${tokens[0]})`;
            const expectedText = getComputedStyle(probe).color;
            probe.style.color = `var(${tokens[1]})`;
            const expectedDot = getComputedStyle(probe).color;
            probe.remove();
            return { text: getComputedStyle(element).color, dot: getComputedStyle(dot).backgroundColor,
              halo: getComputedStyle(halo).backgroundColor, expectedText, expectedDot };
          }, [textToken, dotToken]);
            assert.equal(colors.dot, colors.expectedDot, JSON.stringify(colors));
            assert.equal(colors.text, colors.expectedText, JSON.stringify(colors));
            assert.notEqual(colors.halo, 'rgba(0, 0, 0, 0)');
          }
        }
        assert.deepEqual(opened.problems, []);
      } finally { await opened.close() }
    });
    it(`${viewport.name}:预览说明与独立开关可访问`, async () => {
      const posts: Record<string, unknown>[] = [];
      const state = { enabled: false, available: true, schema: 4,
        fields: ['作品编号、标签、人物、评分与全部偏好维度'],
        notice: '匿名处理后上传馆藏与各类偏好明细。' };
      const opened = await visit(browser, '/settings/library?section=' + encodeURIComponent('应用与维护'), viewport,
        async page => {
          await page.route('**/api/configuration', async route => {
            const response = await route.fetch();
            await route.fulfill({ json: { ...await response.json(), library_sharing: state } });
          });
          await page.route('**/api/configuration/library-sharing/preview', route => route.fulfill({ json: {
            token: 'preview-token', schema: 4, bytes: 128,
            data: { schema: 4, period: '2026-W42', library: [{ code: 'EXM-001', manual_tags: ['长标签'.repeat(500)] }],
              preferences: { performer: [] }, coverage: { uncoded_items: 0 } },
          } }));
          await page.route('**/api/configuration/library-sharing', route => {
            const body = route.request().postDataJSON();
            posts.push(body);
            return route.fulfill({ json: { ...state, enabled: body.enabled } });
          });
        });
      const { page } = opened;
      try {
        const region = page.getByRole('region', { name: '数据上传', exact: true });
        await region.waitFor();
        assert.equal(await region.getByRole('switch').count(), 2, '两项上传开关同属一个分区');
        const initialToggle = region.getByRole('switch', { name: '上传馆藏与口味明细', exact: true });
        assert.equal(await initialToggle.count(), 1, '关闭状态也应显示上传开关');
        const fields = region.getByText(state.fields[0], { exact: true });
        assert.equal(await fields.isVisible(), false, '字段说明默认折叠');
        const rules = region.getByText('馆藏统计字段与发送规则', { exact: true });
        await rules.click();
        assert.equal(await fields.isVisible(), true, '可以展开查看字段');
        await rules.click();
        await fields.waitFor({ state: 'hidden' });
        await region.getByRole('button', { name: '预览上传内容', exact: true }).click();
        const preview = region.getByLabel('馆藏与口味上传预览', { exact: true });
        await preview.waitFor();
        assert.equal(posts.length, 0);
        assert.equal(await fields.isVisible(), false);
        assert.equal(await region.getByText('随机标识用于实例去重。状态每日一次;关闭后仅补发一次关闭状态。', { exact: true }).isVisible(), true);
        assert.match(await preview.innerText(), /EXM-001/);
        assert.match(await preview.innerText(), /performer/);
        assert.equal(await region.locator('[data-code-block]').count(), 1);
        assert.equal(await region.getByRole('button', { name: '复制代码', exact: true }).count(), 1);
        await preview.focus();
        assert.ok(await preview.evaluate(element => element.scrollWidth > element.clientWidth), '长代码在代码块内部横向滚动');
        await settle(page);
        const measured = await layout(page);
        assert.ok(measured.scrollWidth <= measured.viewportWidth + 1, JSON.stringify(measured));
        assert.deepEqual(measured.offenders, []);
        await page.screenshot({ path: fileURLToPath(new URL(`../../build/library-sharing-${viewport.name}.png`, import.meta.url)) });
        const toggle = initialToggle;
        await toggle.locator('xpath=ancestor::label').click();
        await settle(page);
        assert.equal(posts.length, 0);
        await region.getByRole('checkbox').focus();
        await region.getByRole('checkbox').press('Space');
        await region.getByRole('button', { name: '同意并开启上传', exact: true }).click();
        await preview.waitFor({ state: 'hidden' });
        await settle(page);
        assert.deepEqual(posts[0], { enabled: true, schema: 4, consent: true, preview_token: 'preview-token' });
        assert.equal(await preview.count(), 0, '同意后收起数据预览');
        assert.equal(await region.getByRole('checkbox').count(), 0, '同意后收起同意框');
        assert.equal(await fields.isVisible(), false, '同意后字段说明仍保持折叠');
        assert.equal(await toggle.isChecked(), true);
        await region.getByRole('button', { name: '预览上传内容', exact: true }).click();
        await preview.waitFor();
        assert.equal(posts.length, 1, '查看预览不重复保存同意');
        assert.equal(await region.getByRole('checkbox').count(), 0, '已开启时查看预览不重复要求同意');
        await toggle.focus();
        const disabled = page.waitForResponse(response => response.url().endsWith('/api/configuration/library-sharing')
          && response.request().method() === 'POST');
        await toggle.press('Space');
        await disabled;
        await toggle.and(page.locator(':not(:checked)')).waitFor({ state: 'attached' });
        await settle(page);
        assert.equal(posts[1]?.enabled, false);
        assert.equal(await toggle.isChecked(), false);
        assert.deepEqual(opened.problems, []);
      } finally { await opened.close() }
    });
  }
});