136 lines
5.0 KiB
TypeScript
136 lines
5.0 KiB
TypeScript
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\)/)
|
||
})
|
||
})
|