Files
workspace/code/g3soft-libs/packages/ui/e2e/erp-mixed-rail-active.spec.ts
T
2026-10-10 17:33:59 +08:00

136 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from '@playwright/test'
/**
* 双列菜单:第一列选中项样式回归 —— 针对 ERP 应用。
*
* 需求:layout-mixed-rail__item.is-active 的背景要比原来"深一点"、文字白色。
*
* 对照 vben(menu-ui/src/components/menu.vue):
* @mixin menu-item-active (375-379):
* color: var(--menu-item-active-color);
* background: var(--menu-item-active-background-color);
* 深色档赋值 (470-471):
* --menu-item-active-color: hsl(var(--accent-foreground)) ← 高对比文字
* --menu-item-active-background-color: hsl(var(--accent)) ← 强调底色
*
* 判定:选中项背景应比所在列背景明显更深,且文字为亮色(对比度足够)。
*/
const ERP_URL = process.env.ERP_URL ?? 'http://localhost:5090'
async function useMixedNavLayout(page: import('@playwright/test').Page) {
await page.goto(`${ERP_URL}/`)
await page.evaluate(() => {
const KEY = 'g3erp:preferences'
const raw = localStorage.getItem(KEY)
const pref: Record<string, Record<string, unknown>> = raw ? JSON.parse(raw) : {}
const app = pref.app || {}
app.layout = 'sidebar-mixed-nav'
pref.app = app
const sidebar = pref.sidebar || {}
sidebar.enable = true
sidebar.extraCollapse = false
pref.sidebar = sidebar
// 浅色模式,确保测的是浅色档
const theme = pref.theme || {}
theme.mode = 'light'
pref.theme = theme
localStorage.setItem(KEY, JSON.stringify(pref))
for (const key of Object.keys(localStorage)) {
if (key.indexOf('g3erp:app') === 0) localStorage.removeItem(key)
}
})
await page.reload()
await page.waitForTimeout(400)
}
/** 解析 rgb(a) 字符串 → [r,g,b] */
function parseRgb(value: string): [number, number, number] {
const m = value.match(/rgba?\(([^)]+)\)/)
if (!m) throw new Error(`无法解析颜色: ${value}`)
const parts = m[1].split(',').map((s) => Number(s.trim()))
return [parts[0], parts[1], parts[2]]
}
/** 相对亮度(WCAG) */
function luminance([r, g, b]: [number, number, number]): number {
const f = (c: number) => {
const s = c / 255
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4
}
return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b)
}
/** 对比度 */
function contrast(a: [number, number, number], b: [number, number, number]): number {
const la = luminance(a)
const lb = luminance(b)
const [hi, lo] = la > lb ? [la, lb] : [lb, la]
return (hi + 0.05) / (lo + 0.05)
}
test.describe('ERP 双列菜单 - 图标栏选中态', () => {
test('选中项背景比列背景更深,且文字是亮色(对比度足够)', async ({ page }) => {
await useMixedNavLayout(page)
await page.goto(`${ERP_URL}/ocean/booking`)
await page.waitForTimeout(400)
const rail = page.locator('.layout-mixed-rail')
const active = page.locator('.layout-mixed-rail__item.is-active')
await expect(active, '应有一个选中项').toHaveCount(1)
const styles = await page.evaluate(() => {
const railEl = document.querySelector('.layout-mixed-rail') as HTMLElement
const itemEl = document.querySelector('.layout-mixed-rail__item.is-active') as HTMLElement
const cs = getComputedStyle(itemEl)
return {
railBg: getComputedStyle(railEl).backgroundColor,
itemBg: cs.backgroundColor,
itemColor: cs.color,
fontWeight: cs.fontWeight,
}
})
// eslint-disable-next-line no-console
console.log('[active-style]', JSON.stringify(styles))
const railBg = parseRgb(styles.railBg)
const itemBg = parseRgb(styles.itemBg)
const itemColor = parseRgb(styles.itemColor)
// 1) 选中背景确实比列背景更深(亮度更低)
expect(
luminance(itemBg),
`选中背景应比列背景更深:item=${styles.itemBg} rail=${styles.railBg}`,
).toBeLessThan(luminance(railBg))
// 2) 背景不是透明(否则测的是列背景)
expect(styles.itemBg, '选中背景不应是透明').not.toMatch(/rgba\(0,\s*0,\s*0,\s*0\)/)
// 3) 文字与选中背景的对比度达标(可读)
const ratio = contrast(itemColor, itemBg)
expect(ratio, `文字/背景对比度应 >= 4.5,实际 ${ratio.toFixed(2)}`).toBeGreaterThanOrEqual(4.5)
// 4) 文字比背景亮("白色/亮色文字")
expect(
luminance(itemColor),
`文字应比选中背景更亮:text=${styles.itemColor} bg=${styles.itemBg}`,
).toBeGreaterThan(luminance(itemBg))
})
test('未选中项没有深色底(对照)', async ({ page }) => {
await useMixedNavLayout(page)
await page.goto(`${ERP_URL}/ocean/booking`)
await page.waitForTimeout(400)
const inactive = page.locator('.layout-mixed-rail__item:not(.is-active)').first()
const bg = await inactive.evaluate((el) => getComputedStyle(el).backgroundColor)
// 未选中项是透明(继承列的底色)
expect(bg, `未选中项不应有背景填充,实际 ${bg}`).toMatch(/rgba\(0,\s*0,\s*0,\s*0\)/)
})
})