Files
huangzd1997 ff1ffbbfa3 feat(站内通知): 铃铛面板支持时间/内容搜索、按天分组与分页
- 列表接口加 keyword(标题/内容模糊)与 startDate/endDate(年月日闭区间)参数,
  两端控制器透传,服务端补筛选日志
- 两端铃铛面板:搜索框(防抖 300ms)+ 日期区间 + 按年月日分组 + 翻页,
  面板改 Teleport 到 body(挂在顶栏时会被页面 el-select 压住,提 z-index 无效)
- 固化可见范围回归测试:超管全量/管理员只看本组/普通用户只看自己,
  含读写两侧的 user_id+audience 裁剪断言与两端控制器身份来源断言
2026-09-13 23:59:06 +08:00

123 lines
5.4 KiB
TypeScript

import { expect, test, type Page } from '@playwright/test'
// 站内通知铃铛面板验收:关键字/年月日区间搜索、按天分组、分页。
// 依赖 scripts/mock-admin-server.mjs 的通知夹具(14 条:09-13 六条、09-12 四条、09-10 四条)。
async function openBell(page: Page) {
await page.goto('/admin-vue/')
await expect(page.locator('.admin-topbar h1')).toHaveText('用户管理')
await page.locator('.admin-notification-bell .bell-trigger').click()
await expect(page.locator('.bell-panel')).toBeVisible()
await expect(page.locator('.bell-item').first()).toBeVisible()
}
test('test_notification_panel_day_group_and_pagination', async ({ page }) => {
await openBell(page)
const panel = page.locator('.bell-panel')
const nextPage = panel.getByRole('button', { name: '下一页' })
// 每页 10 条,跨两个日期分组,页脚显示页码与总数
await expect(page.locator('.bell-item')).toHaveCount(10)
await expect(page.locator('.bell-day-head')).toHaveCount(2)
await expect(page.locator('.bell-page-info')).toHaveText('1 / 2')
await expect(page.locator('.bell-page-total')).toHaveText('共 14 条')
await expect(page.locator('.bell-day-head').first()).toHaveText(/^(今天|昨天|\d{4}年\d{1,2}月\d{1,2}日)$/)
const page2Request = page.waitForRequest(
(request) => request.url().includes('/api/admin/notifications?') && request.url().includes('page=2'),
)
await nextPage.click()
await page2Request
await expect(page.locator('.bell-item')).toHaveCount(4)
await expect(page.locator('.bell-page-info')).toHaveText('2 / 2')
await expect(page.locator('.bell-day-head')).toHaveCount(1)
await expect(nextPage).toBeDisabled()
})
test('test_notification_panel_search_by_keyword', async ({ page }) => {
await openBell(page)
const keywordRequest = page.waitForRequest((request) =>
decodeURIComponent(request.url()).includes('keyword=余额不足'),
)
await page.locator('.bell-search-input').fill('余额不足')
await keywordRequest
// 关键字命中标题/内容:夹具里 5 条含「余额不足」
await expect(page.locator('.bell-item')).toHaveCount(5)
await expect(page.locator('.bell-page-total')).toHaveText('共 5 条')
await expect(page.locator('.bell-search-reset')).toBeVisible()
await page.locator('.bell-search-reset').click()
await expect(page.locator('.bell-item')).toHaveCount(10)
await expect(page.locator('.bell-search-reset')).toBeHidden()
})
test('test_notification_panel_search_by_day_range', async ({ page }) => {
await openBell(page)
const rangeRequest = page.waitForRequest(
(request) =>
request.url().includes('startDate=2026-09-12') && request.url().includes('endDate=2026-09-12'),
)
await page.locator('.bell-date-input').first().fill('2026-09-12')
await page.locator('.bell-date-input').nth(1).fill('2026-09-12')
await rangeRequest
await expect(page.locator('.bell-item')).toHaveCount(4)
await expect(page.locator('.bell-day-head')).toHaveCount(1)
await expect(page.locator('.bell-page-total')).toHaveText('共 4 条')
await expect(page.locator('.bell-empty')).toHaveCount(0)
// 起止倒挂自动交换:09-13 起 / 09-10 止 → 实际按 09-10 ~ 09-13 查询(全量 14 条)
await page.locator('.bell-date-input').first().fill('2026-09-13')
await page.locator('.bell-date-input').nth(1).fill('2026-09-10')
await expect(page.locator('.bell-page-total')).toHaveText('共 14 条')
await expect(page.locator('.bell-item')).toHaveCount(10)
})
test('test_notification_panel_not_covered_by_page_content', async ({ page }) => {
await openBell(page)
// 回归:面板挂在顶栏内时曾被页面 el-select 压住(提 z-index 无效),现改为 Teleport 到 body。
// 这里在面板内取多点做命中测试,最上层元素必须属于面板自身。
const covered = await page.evaluate(() => {
const panel = document.querySelector('.bell-panel') as HTMLElement
const rect = panel.getBoundingClientRect()
const points: Array<[number, number]> = [
[rect.left + 30, rect.top + 130],
[rect.left + rect.width / 2, rect.top + rect.height / 2],
[rect.right - 30, rect.bottom - 60],
]
return points
.map(([x, y]) => document.elementsFromPoint(x, y)[0])
.filter((el) => !!el && el !== panel && !panel.contains(el))
.map((el) => `${el!.tagName}.${String((el as HTMLElement).className).slice(0, 40)}`)
})
expect(covered, '面板被页面元素遮挡').toEqual([])
})
test('test_notification_panel_reference_screenshot', async ({ page }) => {
await openBell(page)
// 同日多条不得重叠:逐条校验纵向排布严格递增(截图伪影与真实布局问题的分界)
const tops = await page
.locator('.bell-item')
.evaluateAll((els) => els.map((el) => el.getBoundingClientRect().top))
for (let i = 1; i < tops.length; i += 1) {
expect(tops[i], `第 ${i + 1} 条应排在第 ${i} 条下方`).toBeGreaterThan(tops[i - 1])
}
await page.screenshot({ path: 'test-results/notification-panel-admin-page.jpg', animations: 'disabled' })
await page.locator('.bell-panel').screenshot({ path: 'test-results/notification-panel-admin.jpg', animations: 'disabled' })
await page.locator('.bell-search-input').fill('余额不足')
await expect(page.locator('.bell-item')).toHaveCount(5)
await page.locator('.bell-panel').screenshot({
path: 'test-results/notification-panel-admin-search.jpg',
animations: 'disabled',
})
})