|
|
@@ -70,6 +70,44 @@ function envelope(data: unknown) {
|
|
|
return JSON.stringify({ data });
|
|
|
}
|
|
|
|
|
|
+function luminance(color: string) {
|
|
|
+ const channels = color.match(/[\d.]+/g)?.slice(0, 3).map(Number) || [];
|
|
|
+ if (channels.length !== 3) throw new Error(`无法解析颜色:${color}`);
|
|
|
+ const linear = channels.map((value) => {
|
|
|
+ const normalized = value / 255;
|
|
|
+ return normalized <= 0.04045
|
|
|
+ ? normalized / 12.92
|
|
|
+ : ((normalized + 0.055) / 1.055) ** 2.4;
|
|
|
+ });
|
|
|
+ return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2];
|
|
|
+}
|
|
|
+
|
|
|
+function contrastRatio(foreground: string, background: string) {
|
|
|
+ const lighter = Math.max(luminance(foreground), luminance(background));
|
|
|
+ const darker = Math.min(luminance(foreground), luminance(background));
|
|
|
+ return (lighter + 0.05) / (darker + 0.05);
|
|
|
+}
|
|
|
+
|
|
|
+async function expectReadablePair(
|
|
|
+ page: Page,
|
|
|
+ foregroundSelector: string,
|
|
|
+ backgroundSelector: string,
|
|
|
+ minimum = 4.5,
|
|
|
+) {
|
|
|
+ const colors = await page.locator(foregroundSelector).first().evaluate(
|
|
|
+ (foreground, selector) => {
|
|
|
+ const background = document.querySelector(selector);
|
|
|
+ if (!background) throw new Error(`找不到背景元素:${selector}`);
|
|
|
+ return {
|
|
|
+ foreground: getComputedStyle(foreground).color,
|
|
|
+ background: getComputedStyle(background).backgroundColor,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ backgroundSelector,
|
|
|
+ );
|
|
|
+ expect(contrastRatio(colors.foreground, colors.background)).toBeGreaterThanOrEqual(minimum);
|
|
|
+}
|
|
|
+
|
|
|
async function selectChatWorkspace(page: Page, name: string) {
|
|
|
if ((page.viewportSize()?.width || 0) <= 575) {
|
|
|
await page.evaluate(() => window.scrollTo(0, 0));
|
|
|
@@ -480,3 +518,34 @@ test('登录与主要管理视图在不同视口无页面级横向滚动', async
|
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBeLessThanOrEqual(1);
|
|
|
}
|
|
|
});
|
|
|
+
|
|
|
+test('深色导航不污染下拉、状态标签与业务弹层的浅色可读性', async ({ page }) => {
|
|
|
+ await page.goto('/admin/chats');
|
|
|
+ await expect(page.getByRole('heading', { name: '群组管理' })).toBeVisible();
|
|
|
+
|
|
|
+ await page.locator('.bot-selector .ant-select-selector').click();
|
|
|
+ await expect(page.locator('.ant-select-dropdown')).toBeVisible();
|
|
|
+ await expect(page.locator('.ant-select-dropdown')).toHaveCSS('background-color', 'rgb(255, 255, 255)');
|
|
|
+ await expectReadablePair(
|
|
|
+ page,
|
|
|
+ '.ant-select-dropdown .ant-select-item-option-content',
|
|
|
+ '.ant-select-dropdown',
|
|
|
+ );
|
|
|
+
|
|
|
+ await page.keyboard.press('Escape');
|
|
|
+ await expect(page.locator('.ant-table-tbody .ant-tag')).toHaveCSS('background-color', 'rgb(239, 250, 245)');
|
|
|
+ await expectReadablePair(page, '.ant-table-tbody .ant-tag', '.ant-table-tbody .ant-tag');
|
|
|
+
|
|
|
+ await page.goto('/admin/giveaways');
|
|
|
+ await page.getByRole('button', { name: '创建抽奖' }).click();
|
|
|
+ await expect(page.locator('.ant-modal-content')).toHaveCSS('background-color', 'rgb(255, 255, 255)');
|
|
|
+ await expectReadablePair(page, '.ant-modal-title', '.ant-modal-content');
|
|
|
+ await expectReadablePair(page, '.ant-modal .ant-form-item-label label', '.ant-modal-content');
|
|
|
+ await page.locator('.ant-modal-close').click();
|
|
|
+
|
|
|
+ await page.goto('/admin/points');
|
|
|
+ await page.getByRole('button', { name: '手工调整' }).click();
|
|
|
+ await expect(page.locator('.ant-modal-content')).toHaveCSS('background-color', 'rgb(255, 255, 255)');
|
|
|
+ await expectReadablePair(page, '.ant-modal-title', '.ant-modal-content');
|
|
|
+ await expectReadablePair(page, '.ant-modal .ant-form-item-label label', '.ant-modal-content');
|
|
|
+});
|