u
This commit is contained in:
1 parent
5f28a10d89
commit
32d423b759
59 files changed
+5101
-1098
No files matched your search
@@ -0,0 +1,94 @@
|
||||
import { expect, test } from '@playwright/test'
|
||||
|
||||
/**
|
||||
* 回归:**hover 触发**时菜单项高亮「闪一下然后消失」。
|
||||
*
|
||||
* 最小复现(用户指定):文档站 /ui/overlay/dropdown 的「触发方式 → hover 触发」示例。
|
||||
*
|
||||
* 根因(已由开关调用栈 + 逐帧采样证实):
|
||||
* hover 触发下触发器与浮层是两棵 DOM(浮层 Teleport 到 body)。指针从触发器移进
|
||||
* 菜单项时会先后触发:
|
||||
* 1. 触发器 mouseleave(排定关闭定时器,随后被浮层 mouseenter 清掉)
|
||||
* 2. 浮层自己的 mouseenter → setOpen(true) → **再上报一次 open=true**
|
||||
* Dropdown.onOpenChange 曾经「见 true 就 activeIndex = -1」,于是菜单项刚点亮的
|
||||
* 高亮被这次重复上报清掉;指针已停住、不会再派发 mouseenter → 高亮永久不恢复。
|
||||
* click 触发时鼠标停在触发器上,没有这次重复上报,所以只有 hover 模式复现。
|
||||
*
|
||||
* 判定方式刻意避开「指针轨迹」这类不稳定因素:
|
||||
* - 不用逐帧采样(依赖 Playwright 的移动时序,会飘);
|
||||
* - 改为「hover 到项上 → 等足够久 → 断言高亮仍在」。
|
||||
* 修复前:等 100ms(mouseEnterDelay) 后高亮被清 → 断言失败;
|
||||
* 修复后:高亮一直保持 → 断言通过。
|
||||
*/
|
||||
|
||||
test.use({ baseURL: 'http://localhost:3100' })
|
||||
|
||||
test.describe('hover 触发:菜单项高亮不得闪断', () => {
|
||||
test('hover 到菜单项并停留,高亮必须持续保持', async ({ page }) => {
|
||||
await page.goto('/ui/overlay/dropdown')
|
||||
|
||||
const trigger = page.getByRole('button', { name: 'hover 触发' }).first()
|
||||
await expect(trigger).toBeVisible()
|
||||
|
||||
await trigger.hover()
|
||||
const menu = page.locator('.g3-dropdown__menu').first()
|
||||
await expect(menu).toBeVisible({ timeout: 3000 })
|
||||
|
||||
const item = menu.locator('.g3-dropdown__item').first()
|
||||
|
||||
// hover 到菜单项上
|
||||
await item.hover()
|
||||
// 立刻应高亮
|
||||
await expect(item).toHaveClass(/is-item-active/)
|
||||
|
||||
// 关键等待:越过 mouseEnterDelay(100ms) + 若干帧,
|
||||
// 让「浮层重复上报 open → 清 activeIndex」这条路径有机会发生
|
||||
await page.waitForTimeout(400)
|
||||
|
||||
// 修复前这里会失败(高亮已被清掉且不会恢复)
|
||||
await expect(item).toHaveClass(/is-item-active/)
|
||||
// 菜单本身也不该关闭
|
||||
await expect(menu).toBeVisible()
|
||||
})
|
||||
|
||||
test('在两个菜单项之间移动,高亮正确转移且不残留', async ({ page }) => {
|
||||
await page.goto('/ui/overlay/dropdown')
|
||||
const trigger = page.getByRole('button', { name: 'hover 触发' }).first()
|
||||
await trigger.hover()
|
||||
|
||||
const menu = page.locator('.g3-dropdown__menu').first()
|
||||
await expect(menu).toBeVisible({ timeout: 3000 })
|
||||
const items = menu.locator('.g3-dropdown__item')
|
||||
|
||||
await items.nth(0).hover()
|
||||
await expect(items.nth(0)).toHaveClass(/is-item-active/)
|
||||
|
||||
await items.nth(1).hover()
|
||||
await expect(items.nth(1)).toHaveClass(/is-item-active/)
|
||||
await expect(items.nth(0)).not.toHaveClass(/is-item-active/)
|
||||
|
||||
// 停留后仍保持
|
||||
await page.waitForTimeout(400)
|
||||
await expect(items.nth(1)).toHaveClass(/is-item-active/)
|
||||
})
|
||||
|
||||
test('click 触发作为对照组', async ({ page }) => {
|
||||
await page.goto('/ui/overlay/dropdown')
|
||||
|
||||
const clickTrigger = page.getByRole('button', { name: '点击触发' }).first()
|
||||
if ((await clickTrigger.count()) === 0) {
|
||||
test.skip(true, '该页面没有「点击触发」示例按钮')
|
||||
return
|
||||
}
|
||||
|
||||
await clickTrigger.click()
|
||||
const menu = page.locator('.g3-dropdown__menu').first()
|
||||
await expect(menu).toBeVisible()
|
||||
|
||||
const item = menu.locator('.g3-dropdown__item').first()
|
||||
await item.hover()
|
||||
await expect(item).toHaveClass(/is-item-active/)
|
||||
await page.waitForTimeout(400)
|
||||
await expect(item).toHaveClass(/is-item-active/)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user