import { expect, test } from '@playwright/test' /** * 反馈类组件回归。 * * 曾经踩过的坑: * - Message 的方位类名手写成 `g3-message-list--*`(样式表是 `g3-message__list--*`), * 9 个方位全部失效、消息一律堆在左上角; * - Modal 面板不是弹性布局,缩放后内容溢出面板而不是内部滚动。 */ test.describe('Message 方位', () => { test('默认方位是顶部居中', async ({ page }) => { await page.goto('/') await page.getByTestId('toast-default').click() const message = page.locator('.g3-message').first() await expect(message).toBeVisible() const messageBox = (await message.boundingBox())! const viewport = page.viewportSize()! const center = messageBox.x + messageBox.width / 2 expect(Math.abs(center - viewport.width / 2)).toBeLessThan(2) expect(messageBox.y).toBeLessThan(viewport.height * 0.25) }) test('指定方位生效:左上 / 右下', async ({ page }) => { await page.goto('/') const viewport = page.viewportSize()! await page.getByTestId('toast-top-left').click() const topLeft = (await page.locator('.g3-message').first().boundingBox())! expect(topLeft.x).toBeLessThan(48) expect(topLeft.y).toBeLessThan(viewport.height * 0.25) await page.waitForTimeout(3200) await page.getByTestId('toast-bottom-right').click() const bottomRight = (await page.locator('.g3-message').first().boundingBox())! expect(bottomRight.x + bottomRight.width).toBeGreaterThan(viewport.width - 48) expect(bottomRight.y).toBeGreaterThan(viewport.height * 0.6) }) }) test.describe('Notification', () => { test('默认出现在右上角', async ({ page }) => { await page.goto('/') const viewport = page.viewportSize()! await page.getByTestId('notify').click() const notification = page.locator('.g3-notification').first() await expect(notification).toBeVisible() const box = (await notification.boundingBox())! expect(box.x + box.width).toBeGreaterThan(viewport.width - 48) expect(box.y).toBeLessThan(viewport.height * 0.3) }) }) test.describe('Modal', () => { test('缩放变矮后内容区内部滚动,而不是溢出面板', async ({ page }) => { await page.goto('/') await page.getByTestId('open-modal').click() const modal = page.locator('.g3-modal') await expect(modal).toBeVisible() // 把南侧手柄往上拖,压缩面板高度 const handle = (await page.locator('.g3-modal__resizer-s').boundingBox())! const startX = handle.x + handle.width / 2 await page.mouse.move(startX, handle.y + handle.height / 2) await page.mouse.down() await page.mouse.move(startX, handle.y - 400, { steps: 8 }) await page.mouse.up() const panel = (await modal.boundingBox())! const body = await page.evaluate(() => { const el = document.querySelector('.g3-modal__body') as HTMLElement return { client: el.clientHeight, scroll: el.scrollHeight } }) // 面板被压矮,内容比可视区高 → 必须内部滚动 expect(panel.height).toBeLessThan(700) expect(body.scroll).toBeGreaterThan(body.client) }) test('Esc 关闭,遮罩点击可关闭', async ({ page }) => { await page.goto('/') await page.getByTestId('open-modal').click() await expect(page.locator('.g3-modal')).toBeVisible() await page.keyboard.press('Escape') await expect(page.locator('.g3-modal')).toBeHidden() await page.getByTestId('open-modal').click() await page.mouse.click(60, 400) await expect(page.locator('.g3-modal')).toBeHidden() }) test('焦点被限制在面板内(Tab 循环)', async ({ page }) => { await page.goto('/') await page.getByTestId('open-modal').click() await expect(page.locator('.g3-modal')).toBeVisible() for (let index = 0; index < 8; index += 1) { await page.keyboard.press('Tab') } const inside = await page.evaluate(() => { const panel = document.querySelector('.g3-modal') return panel ? panel.contains(document.activeElement) : false }) expect(inside).toBe(true) }) }) test.describe('Modal.confirm(命令式)', () => { test('点确定后按钮进入 loading,完成后自动关闭', async ({ page }) => { await page.goto('/') await page.getByTestId('open-confirm').click() const modal = page.locator('.g3-modal') await expect(modal).toBeVisible() await expect(modal.locator('.g3-modal__title')).toHaveText('删除确认') const confirmButton = modal.locator('.g3-modal__footer button').last() await expect(confirmButton).toHaveText('确定') await confirmButton.click() // loading 期间按钮禁用且带 aria-busy await expect(confirmButton).toBeDisabled() await expect(confirmButton).toHaveAttribute('aria-busy', 'true') // onConfirm 的 Promise resolve 后自动关闭 await expect(modal).toBeHidden({ timeout: 5000 }) }) })