frontend/e2e/library-sharing.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 { 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() }
});
}
});