Files
workspace/code/g3soft-libs/packages/ui/e2e/select.spec.ts
T
2026-10-08 22:38:34 +08:00

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)
})
})