This commit is contained in:
oneao committed 2026-10-12 17:32:50 +08:00
1 parent 78eb811ff7
commit 97fe2ff306
83 files changed
+10083 -3662

No files matched your search

@@ -14,13 +14,20 @@ import { expect, test, type Page } from '@playwright/test'
* 这些都是布局结果,只能放 E2E(happy-dom 没有布局引擎)。
*/
/** 插入表格(cols 为列数,默认 3 行 3 列) */
async function insertTable(page: Page, cols?: number): Promise<void> {
/**
* 插入表格。
*
* 面板已从「行数 / 列数」两个数字输入框换成**网格选择器**(对齐 TinyMCE):
* 移入 (rows, cols) 那个格子即高亮对应矩形,点击插入。所以这里改成点格子,
* 不再有「插入」按钮需要按。
*/
async function insertTable(page: Page, cols = 3, rows = 3): Promise<void> {
const editor = page.getByTestId('rich-text')
await editor.scrollIntoViewIfNeeded()
await editor.locator('button[aria-label="插入表格"]').click()
if (cols) await page.locator('.g3-rich-text__table-input').nth(1).fill(String(cols))
await page.getByRole('button', { name: '插入', exact: true }).click()
const cell = page.locator(`.g3-rich-text__table-grid-cell[data-row="${rows}"][data-col="${cols}"]`)
await cell.hover()
await cell.click()
await expect(editor.locator('table')).toHaveCount(1)
await editor.locator('table').scrollIntoViewIfNeeded()
}
@@ -145,41 +152,64 @@ test.describe('RichText 表格', () => {
}
})
test('插入表格面板:label 与输入框上下排列且输入框不溢出', async ({ page }) => {
test('插入表格面板:网格选择器高亮矩形并实时显示尺寸', async ({ page }) => {
await page.goto('/')
const editor = page.getByTestId('rich-text')
await editor.scrollIntoViewIfNeeded()
await editor.locator('button[aria-label="插入表格"]').click()
const fields = page.locator('.g3-rich-text__table-field')
// 行数 / 列数 / 包含表头行 三个字段
await expect(fields).toHaveCount(3)
// 8 行 × 10 列的网格
const cells = page.locator('.g3-rich-text__table-grid-cell')
await expect(cells).toHaveCount(80)
// 只对含数字输入框的两个字段做上下排列 + 不溢出校验
const geometry = await fields.evaluateAll((els) =>
els.flatMap((el) => {
const label = el.querySelector('.g3-rich-text__table-label')!.getBoundingClientRect()
const input = el.querySelector('.g3-rich-text__table-input')
if (!input) return []
const rect = input.getBoundingClientRect()
return [
{
labelBottom: label.bottom,
inputTop: rect.top,
inputWidth: rect.width,
fieldWidth: el.getBoundingClientRect().width,
},
]
}),
// 尺寸提示:未悬停时显示默认 3 × 3
const label = page.locator('.g3-rich-text__table-grid-label')
await expect(label).toHaveText('3 × 3')
// 悬停 4 行 6 列 → 高亮 24 格,提示同步
await page.locator('.g3-rich-text__table-grid-cell[data-row="4"][data-col="6"]').hover()
await expect(label).toHaveText('4 × 6')
await expect(page.locator('.g3-rich-text__table-grid-cell.is-table-grid-cell-active')).toHaveCount(
24,
)
expect(geometry).toHaveLength(2)
for (const item of geometry) {
// 输入框在 label 下方
expect(item.inputTop).toBeGreaterThanOrEqual(item.labelBottom)
// content-box + width:100% 曾让输入框比容器宽 18px(padding + border)
expect(Math.round(item.inputWidth)).toBeLessThanOrEqual(Math.round(item.fieldWidth) + 1)
}
// 悬停更小的区域 → 高亮随之收缩(不是只增不减)
await page.locator('.g3-rich-text__table-grid-cell[data-row="2"][data-col="2"]').hover()
await expect(label).toHaveText('2 × 2')
await expect(page.locator('.g3-rich-text__table-grid-cell.is-table-grid-cell-active')).toHaveCount(
4,
)
})
test('插入表格面板:不溢出容器,含表头行勾选可用', async ({ page }) => {
await page.goto('/')
const editor = page.getByTestId('rich-text')
await editor.scrollIntoViewIfNeeded()
await editor.locator('button[aria-label="插入表格"]').click()
const form = page.locator('.g3-rich-text__table-form')
const grid = page.locator('.g3-rich-text__table-grid')
const formBox = (await form.boundingBox())!
const gridBox = (await grid.boundingBox())!
// 网格必须落在面板内(早期手写样式曾让内容比容器宽 18px)
expect(gridBox.width).toBeLessThanOrEqual(formBox.width + 1)
expect(gridBox.x).toBeGreaterThanOrEqual(formBox.x - 1)
// 「包含表头行」默认选中;取消后插入的表首行应为 td 而非 th。
// 注意:点击**包裹用的 label**,不能对内部 input 调 uncheck() ——
// G3Checkbox 的原生 input 是 1px + opacity:0 + pointer-events:none(视觉由 span 承担),
// Playwright 认为它不可见 / 不可交互,uncheck() 会直接超时。
const toggleInput = page.locator('.g3-rich-text__table-checkbox input')
const toggleLabel = page.locator('.g3-rich-text__table-header-toggle')
await expect(toggleInput).toBeChecked()
await toggleLabel.click()
await expect(toggleInput).not.toBeChecked()
await page.locator('.g3-rich-text__table-grid-cell[data-row="2"][data-col="2"]').click()
await expect(editor.locator('table tr')).toHaveCount(2)
await expect(editor.locator('table')).toHaveCount(1)
await expect(editor.locator('table tr:first-child th')).toHaveCount(0)
})
})
@@ -371,7 +401,10 @@ test.describe('RichText 表格 · 操作浮层', () => {
// 操作栏里已不再出现单元格对齐按钮(对齐统一走顶部工具条)
await expect(menu.getByRole('button', { name: '单元格居中' })).toHaveCount(0)
await expect(menu.getByRole('button', { name: '取消单元格对齐' })).toHaveCount(0)
// 「表头」整组已移除:切换表头列 / 切换表头单元格都不再提供
await expect(menu.getByRole('button', { name: '切换表头行' })).toHaveCount(0)
await expect(menu.getByRole('button', { name: '切换表头列' })).toHaveCount(0)
await expect(menu.getByRole('button', { name: '切换表头单元格' })).toHaveCount(0)
// 删除行 / 删除列的图标是自绘 SVG(不再是拆分行列的 lucide 图标)
for (const name of ['删除行', '删除列']) {
@@ -387,23 +420,6 @@ test.describe('RichText 表格 · 操作浮层', () => {
await expect(editor.locator('table tr:first-child th')).toHaveCount(2)
})
test('切换表头列 / 表头单元格', async ({ page }) => { await page.goto('/')
await insertTable(page)
const editor = page.getByTestId('rich-text')
const menu = page.locator('.g3-rich-text-table-menu')
// 表头单元格:把普通单元格切换为 th
await openMenuInCell(page, 1)
await menu.getByRole('button', { name: '切换表头单元格' }).click()
await expect(editor.locator('table tr').nth(1).locator('th')).toHaveCount(1)
// 表头列:整列切换为 th(第 2、3 行的首格都变 th;首行本就是表头行不受影响)
await menu.getByRole('button', { name: '切换表头列' }).click()
await expect(editor.locator('table tr').nth(1).locator('th')).toHaveCount(1)
await expect(editor.locator('table tr').nth(2).locator('th')).toHaveCount(1)
await expect(editor.locator('table tr').nth(1).locator('td')).toHaveCount(2)
})
test('单元格内文字的对齐走顶部工具条', async ({ page }) => {
await page.goto('/')
await insertTable(page)
@@ -418,28 +434,29 @@ test.describe('RichText 表格 · 操作浮层', () => {
await expect(cell.locator('p')).toHaveAttribute('style', /text-align:\s*center/)
})
test('插入面板行列数超界被夹回合法范围,且可关闭表头行', async ({ page }) => {
/**
* 「超界被夹回」这条用例已随数字输入框一起移除:网格选择器的可选范围由网格本身
* 硬性限定(8 行 × 10 列),用户在结构上就填不出 0 行或 99 列 —— 也就没有「夹回」可言。
* 这里改为断言这个上限本身,防止有人无意中把网格尺寸改大或改小。
*/
test('网格选择器的上限为 8 行 × 10 列,且只能选到边界内的格子', async ({ page }) => {
await page.goto('/')
const editor = page.getByTestId('rich-text')
await editor.scrollIntoViewIfNeeded()
await editor.locator('button[aria-label="插入表格"]').click()
// 输入非法值(0 行 / 99 列)
await page.locator('.g3-rich-text__table-input').nth(0).fill('0')
await page.locator('.g3-rich-text__table-input').nth(1).fill('99')
await page.getByRole('button', { name: '插入', exact: true }).click()
const cells = page.locator('.g3-rich-text__table-grid-cell')
await expect(cells).toHaveCount(80)
// 边界格子存在,越界的不存在
await expect(page.locator('.g3-rich-text__table-grid-cell[data-row="8"][data-col="10"]')).toHaveCount(1)
await expect(page.locator('.g3-rich-text__table-grid-cell[data-row="9"]')).toHaveCount(0)
await expect(page.locator('.g3-rich-text__table-grid-cell[data-col="11"]')).toHaveCount(0)
// 选边界值:8 行 10 列能正常插入
await page.locator('.g3-rich-text__table-grid-cell[data-row="8"][data-col="10"]').click()
const table = editor.locator('table')
await expect(table).toHaveCount(1)
// 0 → 1 行,99 → 20 列
await expect(table.locator('tr')).toHaveCount(1)
await expect(table.locator('tr:first-child th')).toHaveCount(20)
// 关闭表头行:插入后首行是 td 而非 th
await editor.locator('button[aria-label="插入表格"]').click()
await page.locator('.g3-rich-text__table-checkbox').uncheck()
await page.getByRole('button', { name: '插入', exact: true }).click()
await expect(editor.locator('table')).toHaveCount(2)
await expect(editor.locator('table').nth(1).locator('tr:first-child th')).toHaveCount(0)
await expect(table.locator('tr')).toHaveCount(8)
await expect(table.locator('tr:first-child th')).toHaveCount(10)
})
})