fix: 导入 Element Plus 对话框与确认框样式,弹窗不再是文档流块 (#37)
- main.ts 补上 dialog 与 message-box 的样式导入:自 #10 起 ElDialog/ElMessageBox 一直 缺少样式,退化成列表后面的普通块(用户报告「编辑区显示在章节列表下面」) - 新增 e2e/overlay.ts:expectModalOverlay / expectMessageBoxOverlay 断言弹窗位于 .el-overlay(position: fixed)、在视口内且横向不溢出 - attachments.spec.ts(章节编辑对话框、插图弹窗)与 edit.spec.ts(书名对话框、删除确认) 都改用该断言,堵住「只看可见性、不看是否浮层」的验证盲区 - 真实链路确认:对话框遮罩为 rgba(0,0,0,0.5)、居中于视口;确认框同样为固定浮层 - 文档:Local-Development 记录缺陷、根因、修复与新增回归断言
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { expect, test, type Page } from '@playwright/test'
|
||||
import { expectModalOverlay } from './overlay'
|
||||
|
||||
// Book audio and covers against a mocked API: upload, replace, remove, and the reader's player.
|
||||
const user = { id: 42, username: 'fictional-attachment', role: 'learner' }
|
||||
@@ -114,7 +115,8 @@ test('the library shows the cover and the chapter dialog manages its files', asy
|
||||
await page.screenshot({ path: '../.local/evidence/issue37-chapter-row.png' })
|
||||
|
||||
await page.getByTestId('edit-chapter-3').click()
|
||||
await expect(page.getByTestId('chapter-dialog')).toBeVisible()
|
||||
// A real modal, not content appended below the chapter list.
|
||||
await expectModalOverlay(page, page.getByTestId('chapter-dialog'))
|
||||
await expect(page.locator('#chapter-title')).toBeVisible()
|
||||
await expect(page.getByTestId('attachment-timing')).toContainText('立即上传')
|
||||
await expect(page.getByTestId('illustration-state')).toContainText('已设置')
|
||||
@@ -180,7 +182,7 @@ test('the reader plays the audio without starting it and remembers the position'
|
||||
await expect(thumbnail).toBeVisible()
|
||||
await expect(thumbnail).toContainText('点击查看大图')
|
||||
await thumbnail.click()
|
||||
await expect(page.getByTestId('illustration-dialog')).toBeVisible()
|
||||
await expectModalOverlay(page, page.getByTestId('illustration-dialog'))
|
||||
await expect(page.getByTestId('illustration-dialog').locator('img')).toHaveAttribute('src', /^blob:/)
|
||||
await page.screenshot({ path: '../.local/evidence/issue37-illustration-dialog.png' })
|
||||
await page.keyboard.press('Escape')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect, test, type Page } from '@playwright/test'
|
||||
import { expectMessageBoxOverlay, expectModalOverlay } from './overlay'
|
||||
|
||||
// Renaming, editing and deleting through the real dialogs of the accepted prototype, against
|
||||
// a mocked API.
|
||||
@@ -69,6 +70,7 @@ test('rename the book, edit a chapter into a new version and delete both', async
|
||||
|
||||
// Rename the book through the dialog.
|
||||
await page.getByTestId('edit-book').click()
|
||||
await expectModalOverlay(page, page.getByTestId('book-dialog'))
|
||||
await expect(page.getByLabel('书名', { exact: true })).toHaveValue('A small step')
|
||||
await page.getByLabel('书名', { exact: true }).fill('A long step')
|
||||
await page.getByTestId('save-book').click()
|
||||
@@ -93,6 +95,8 @@ test('rename the book, edit a chapter into a new version and delete both', async
|
||||
await page.locator('.el-message-box').last().getByRole('button', { name: '取消' }).click()
|
||||
expect(requests.filter(entry => entry === 'DELETE /api/v1/chapters/9')).toHaveLength(0)
|
||||
await page.getByTestId('delete-chapter').click()
|
||||
// A confirmation is a modal overlay too, not text appended to the page.
|
||||
await expectMessageBoxOverlay(page)
|
||||
await page.locator('.el-message-box').last().getByRole('button', { name: '确认删除章节' }).click()
|
||||
await expect(page.getByTestId('book-notice')).toContainText('章节已删除 · 剩余 1 章')
|
||||
await expect(page.locator('.chapter-row')).toHaveCount(1)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { expect, type Locator, type Page } from '@playwright/test'
|
||||
|
||||
/**
|
||||
* Asserts that a dialog is a modal overlay rather than a block in the document flow.
|
||||
*
|
||||
* A missing Element Plus stylesheet is invisible to `toBeVisible()` and to the unit tests, but it
|
||||
* turns every dialog into content appended after the list it was opened from — which is exactly how
|
||||
* a reader reported it. These checks need a real browser with the real stylesheets.
|
||||
*/
|
||||
export async function expectModalOverlay(page: Page, dialog: Locator): Promise<void> {
|
||||
await expect(dialog).toBeVisible()
|
||||
const layout = await dialog.evaluate(element => {
|
||||
const box = element.getBoundingClientRect()
|
||||
const overlay = element.closest('.el-overlay')
|
||||
return {
|
||||
overlay: overlay ? getComputedStyle(overlay).position : 'none',
|
||||
position: getComputedStyle(element).position,
|
||||
top: Math.round(box.top),
|
||||
left: Math.round(box.left),
|
||||
width: Math.round(box.width),
|
||||
viewportHeight: window.innerHeight,
|
||||
viewportWidth: window.innerWidth,
|
||||
}
|
||||
})
|
||||
expect(layout.overlay, 'the dialog must render inside a fixed overlay').toBe('fixed')
|
||||
expect(layout.position, 'the dialog itself is positioned by the overlay').not.toBe('static')
|
||||
// It is laid out over the viewport, not below the page content that opened it.
|
||||
expect(layout.top, 'the dialog must start inside the viewport').toBeGreaterThanOrEqual(0)
|
||||
expect(layout.top, 'the dialog must not be pushed below the viewport').toBeLessThan(layout.viewportHeight)
|
||||
expect(layout.viewportWidth - (layout.left + layout.width), 'the dialog must fit horizontally').toBeGreaterThanOrEqual(-2)
|
||||
}
|
||||
|
||||
/** Asserts that a message box is a modal overlay as well. */
|
||||
export async function expectMessageBoxOverlay(page: Page): Promise<void> {
|
||||
const box = page.locator('.el-message-box')
|
||||
await expect(box).toBeVisible()
|
||||
const overlay = await box.evaluate(element => {
|
||||
const wrapper = element.closest('.el-overlay')
|
||||
return wrapper ? getComputedStyle(wrapper).position : 'none'
|
||||
})
|
||||
expect(overlay, 'the confirmation must render inside a fixed overlay').toBe('fixed')
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import 'element-plus/es/components/radio/style/css'
|
||||
import 'element-plus/es/components/radio-group/style/css'
|
||||
import 'element-plus/es/components/select/style/css'
|
||||
import 'element-plus/es/components/option/style/css'
|
||||
import 'element-plus/es/components/dialog/style/css'
|
||||
import 'element-plus/es/components/message-box/style/css'
|
||||
import 'element-plus/es/components/dropdown/style/css'
|
||||
import 'element-plus/es/components/dropdown-menu/style/css'
|
||||
import 'element-plus/es/components/dropdown-item/style/css'
|
||||
|
||||
Reference in New Issue
Block a user