import { expect, test } from '@playwright/test' /** * Select 回归。 * * 最严重的一次事故:unwrapped 触发器每次渲染都换新的组件对象, * `` 判定类型变化 → 卸载重建触发器 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) }) })