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

110 lines
4.4 KiB
TypeScript

import { expect, test } from '@playwright/test'
/**
* 双列菜单布局(layout = 'sidebar-mixed-nav')渲染回归 —— 针对 ERP 应用。
*
* 组件编过 ≠ 页面渲染对,所以这里在真实浏览器里断言 DOM 结构:
* 1. 切到双列布局后,左侧图标栏 + 第二列都存在;
* 2. 单列侧栏(.layout-sidebar)不再渲染;
* 3. 第一列只显示顶级项(图标 + 标题),不含子项;
* 4. 第二列显示当前顶级分组的子项;
* 5. 点第一列另一个分组,第二列内容随之切换。
*
* 说明:放在组件库 e2e 工程里跑(已有 Playwright 配置),
* 通过 ERP_URL 指向 ERP dev server,避免给 ERP 应用引入 Playwright 依赖。
*/
const ERP_URL = process.env.ERP_URL ?? 'http://localhost:5090'
/** 通过偏好设置把布局切成双列菜单(直接写 localStorage,避免点 UI 的耦合) */
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
pref.sidebar = sidebar
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)
}
test.describe('ERP 双列菜单布局', () => {
test('渲染出图标栏 + 第二列,且不再渲染单列侧栏', async ({ page }) => {
await useMixedNavLayout(page)
// 布局根节点带上了对应 class
await expect(page.locator('.layout--sidebar-mixed-nav')).toHaveCount(1)
// 两列都在
await expect(page.locator('.layout-mixed-rail')).toBeVisible()
await expect(page.locator('.layout-mixed-column')).toBeVisible()
// 单列侧栏不该出现
await expect(page.locator('.layout-sidebar')).toHaveCount(0)
})
test('第一列只放顶级项,第二列放该分组的子项', async ({ page }) => {
await useMixedNavLayout(page)
// 进入海运下的某个叶子,使「海运」成为当前顶级分组
await page.goto(`${ERP_URL}/ocean/booking`)
await page.waitForTimeout(400)
const railItems = page.locator('.layout-mixed-rail__item')
await expect(railItems.first()).toBeVisible()
// 第一列项数应等于顶级菜单数(6 个业务域)
const railCount = await railItems.count()
expect(railCount, '第一列应为顶级菜单数量').toBeGreaterThanOrEqual(5)
// 第一列里不应出现二级标题(如「订舱单」)
const railText = (await railItems.allTextContents()).join('|')
expect(railText, '第一列不应包含子项「订舱单」').not.toContain('订舱单')
expect(railText, '第一列应含顶级项「海运」').toContain('海运')
// 第一列里「海运」应是选中态
const activeRail = page.locator('.layout-mixed-rail__item.is-active')
await expect(activeRail).toHaveCount(1)
await expect(activeRail).toContainText('海运')
// 第二列显示「海运」的子项
const columnText = await page.locator('.layout-mixed-column').innerText()
expect(columnText, '第二列应含「海运」的子项').toContain('订舱单')
})
test('点第一列切换到别的分组,第二列内容随之变化', async ({ page }) => {
await useMixedNavLayout(page)
await page.goto(`${ERP_URL}/ocean/booking`)
await page.waitForTimeout(400)
const columnBefore = await page.locator('.layout-mixed-column').innerText()
expect(columnBefore).toContain('订舱单')
// 点第一列的「财务」
await page.locator('.layout-mixed-rail__item', { hasText: '财务' }).click()
await page.waitForTimeout(500)
const columnAfter = await page.locator('.layout-mixed-column').innerText()
expect(columnAfter, '切到财务后第二列应显示财务的子项').toContain('应收费用')
expect(columnAfter, '第二列不应再显示海运的子项').not.toContain('订舱单')
// 「财务」应变成选中态
await expect(page.locator('.layout-mixed-rail__item.is-active')).toContainText('财务')
})
})