82 lines
3.6 KiB
TypeScript
82 lines
3.6 KiB
TypeScript
import { expect, test } from '@playwright/test'
|
|
|
|
/**
|
|
* Select 回归。
|
|
*
|
|
* 最严重的一次事故:unwrapped 触发器每次渲染都换新的组件对象,
|
|
* `<component :is>` 判定类型变化 → 卸载重建触发器 DOM → 焦点丢失派发 focusout
|
|
* → 处理器改状态 → 再重建……自我驱动成死循环,点击一下就把主线程打满。
|
|
* 所以这里用 MutationObserver 数「DOM 节点增删次数」来锁住「触发器不被重建」。
|
|
*/
|
|
|
|
test.describe('Select 交互', () => {
|
|
test('打开下拉不会重建触发器 DOM(死循环回归)', async ({ page }) => {
|
|
await page.goto('/')
|
|
|
|
await page.evaluate(() => {
|
|
;(window as unknown as { __mut: number }).__mut = 0
|
|
new MutationObserver((records) => {
|
|
const target = window as unknown as { __mut: number }
|
|
for (const record of records) {
|
|
target.__mut += record.addedNodes.length + record.removedNodes.length
|
|
}
|
|
}).observe(document.body, { childList: true, subtree: true })
|
|
})
|
|
|
|
const trigger = page.locator('[data-testid="select-basic"] .g3-select__trigger')
|
|
await trigger.click()
|
|
await expect(page.locator('.g3-select__option').first()).toBeVisible()
|
|
await page.waitForTimeout(400)
|
|
|
|
const mutations = await page.evaluate(() => (window as unknown as { __mut: number }).__mut)
|
|
// 只有浮层挂载带来的少量节点变化;重建触发器会产生成百上千次
|
|
expect(mutations).toBeLessThan(20)
|
|
|
|
// 焦点应稳定停留在触发器输入框上
|
|
const focused = await page.evaluate(() => document.activeElement?.className ?? '')
|
|
expect(focused).toContain('g3-select__input')
|
|
})
|
|
|
|
test('选中选项后回填并关闭', async ({ page }) => {
|
|
await page.goto('/')
|
|
const root = page.locator('[data-testid="select-basic"]')
|
|
|
|
await root.locator('.g3-select__trigger').click()
|
|
await page.locator('.g3-select__option').filter({ hasText: '会员' }).click()
|
|
|
|
await expect(root.locator('input')).toHaveValue('会员')
|
|
await expect(page.locator('.g3-select__dropdown')).toHaveCount(0)
|
|
})
|
|
|
|
test('多选以「、」拼接回显,且选中后保持展开', async ({ page }) => {
|
|
await page.goto('/')
|
|
const root = page.locator('[data-testid="select-multiple"]')
|
|
|
|
await root.locator('.g3-select__trigger').click()
|
|
await page.locator('.g3-select__option').filter({ hasText: '管理员' }).click()
|
|
await page.locator('.g3-select__option').filter({ hasText: '会员' }).click()
|
|
|
|
await expect(root.locator('input')).toHaveValue('管理员、会员')
|
|
await expect(page.locator('.g3-select__dropdown')).toBeVisible()
|
|
})
|
|
|
|
test('下拉宽度跟随触发框,且不超过上限', async ({ page }) => {
|
|
await page.goto('/')
|
|
const root = page.locator('[data-testid="select-basic"]')
|
|
const triggerWidth = (await root.locator('.g3-select__trigger').boundingBox())!.width
|
|
|
|
await root.locator('.g3-select__trigger').click()
|
|
// playground 里还有两个常驻 Popover,用内容过滤精确定位本用例的浮层
|
|
const popup = page.locator('.g3-popover__popup').filter({ has: page.locator('.g3-select__dropdown') })
|
|
await expect(popup).toBeVisible()
|
|
|
|
const popupWidth = (await popup.boundingBox())!.width
|
|
// 下拉宽度 = max(触发框宽, 内容宽):触发框更宽时下拉跟着变宽(antd 行为),
|
|
// 样式里的 max-width 只在触发框较窄、内容很长时起限制作用
|
|
expect(popupWidth).toBeGreaterThanOrEqual(Math.floor(triggerWidth) - 1)
|
|
|
|
const viewport = page.viewportSize()!
|
|
expect(popupWidth).toBeLessThanOrEqual(viewport.width)
|
|
})
|
|
})
|