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

@@ -0,0 +1,162 @@
import { expect, test } from '@playwright/test'
/**
* G3Table 的几何 / 命中类断言 —— happy-dom 没有布局引擎,这些只能在真浏览器里量。
*
* 用例只按 data-testid 找元素(不依赖文案与结构),宿主页在 playground/main.ts。
*/
test.describe('G3Table 表格', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.locator('[data-testid="table"]').scrollIntoViewIfNeeded()
})
test('基础渲染:表头与数据行都在,且序号列存在', async ({ page }) => {
const table = page.locator('[data-testid="table-wrap"]')
await expect(table.locator('.stk-table')).toBeVisible()
// 表头至少有我们的业务列文案
await expect(table.locator('th', { hasText: '名称' })).toBeVisible()
await expect(table.locator('th', { hasText: '金额' })).toBeVisible()
// 数据行渲染出来了
const rows = table.locator('.stk-tbody-main tr')
await expect(rows).toHaveCount(3)
})
test('小数据量(3 行):纵向虚拟滚动关闭,DOM 里是真实 3 行', async ({ page }) => {
const table = page.locator('[data-testid="table-wrap"]')
// 库开启虚拟滚动时会给根加 .virtual 类;门控生效时不应有
await expect(table.locator('.stk-table')).not.toHaveClass(/virtual/)
})
test('大数据量(5000 行):纵向虚拟滚动开启,DOM 行数远少于数据量', async ({ page }) => {
const big = page.locator('[data-testid="table-big-wrap"]')
await big.scrollIntoViewIfNeeded()
await expect(big.locator('.stk-table')).toHaveClass(/virtual/)
// 虚拟滚动的意义:只渲染视口内若干行,DOM 行数应当是个位数/几十,而不是 5000
const rendered = await big.locator('.stk-tbody-main tr').count()
expect(rendered).toBeGreaterThan(0)
expect(rendered).toBeLessThan(200)
})
test('大数据量:滚动后仍只渲染视口内行(虚拟窗口跟随)', async ({ page }) => {
const big = page.locator('[data-testid="table-big-wrap"]')
await big.scrollIntoViewIfNeeded()
const scroller = big.locator('.stk-table-scroll-container, .stk-table-main').first()
await scroller.evaluate((el) => {
el.scrollTop = 3000
})
await page.waitForTimeout(150)
const rendered = await big.locator('.stk-tbody-main tr').count()
expect(rendered).toBeGreaterThan(0)
expect(rendered).toBeLessThan(200)
})
test('高度约束:容器限高时表格不溢出', async ({ page }) => {
const wrap = page.locator('[data-testid="table-wrap"]')
const wrapBox = await wrap.boundingBox()
const inner = wrap.locator('.stk-table').first()
const innerBox = await inner.boundingBox()
expect(wrapBox).not.toBeNull()
expect(innerBox).not.toBeNull()
// 表格不应高于它的容器(留 2px 容差:边框/亚像素)
expect(innerBox!.height).toBeLessThanOrEqual(wrapBox!.height + 2)
})
test('外框圆角:bordered 时容器有圆角与描边', async ({ page }) => {
// 注意:data-testid 透传给了内层 StkTable(组件 inheritAttrs: false),
// 外框与圆角在我们自己的根节点 .g3-table 上,所以要按类名定位。
const root = page.locator('[data-testid="table-wrap"] .g3-table').first()
await expect(root).toHaveClass(/is-bordered/)
const radius = await root.evaluate((el) => getComputedStyle(el).borderRadius)
expect(radius).not.toBe('0px')
const borderWidth = await root.evaluate((el) => getComputedStyle(el).borderTopWidth)
expect(borderWidth).toBe('1px')
})
test('表头显示序号列(seq 默认开启)', async ({ page }) => {
const table = page.locator('[data-testid="table-wrap"]')
await expect(table.locator('th', { hasText: '序号' })).toBeVisible()
})
test('窄容器下不横向溢出容器宽度', async ({ page }) => {
const wrap = page.locator('[data-testid="table-narrow-wrap"]')
await wrap.scrollIntoViewIfNeeded()
const wrapBox = await wrap.boundingBox()
const rootBox = await wrap.locator('[data-testid="table-narrow"]').boundingBox()
expect(rootBox!.width).toBeLessThanOrEqual(wrapBox!.width + 2)
})
})
test.describe('G3Table 选择列(真浏览器命中)', () => {
test('点击行复选框:勾选态可见地变化', async ({ page }) => {
await page.goto('/')
const wrap = page.locator('[data-testid="table-wrap"]')
await wrap.scrollIntoViewIfNeeded()
const firstRowCheckbox = wrap.locator('.g3-table__checkbox').first()
await firstRowCheckbox.scrollIntoViewIfNeeded()
await expect(firstRowCheckbox).not.toHaveClass(/is-checked/)
await firstRowCheckbox.click()
await expect(firstRowCheckbox).toHaveClass(/is-checked/)
})
test('每行复选框数 = 选择列 + checkbox 编辑器列(演示数据定义了两列)', async ({ page }) => {
await page.goto('/')
const wrap = page.locator('[data-testid="table-wrap"]')
await wrap.scrollIntoViewIfNeeded()
// 演示数据的 tableColumns 里有两个复选框列:选择列(注入)+ 启用列(editor: checkbox)。
// 所以每行 2 个是预期值——这正是为什么不能靠「数复选框个数」判断是否有重复渲染。
const bodyRows = wrap.locator('.stk-tbody-main tr')
await expect(bodyRows).toHaveCount(3)
for (let i = 0; i < 3; i += 1) {
await expect(bodyRows.nth(i).locator('.g3-table__checkbox')).toHaveCount(2)
}
})
test('无重复渲染:每行 td 数等于表头列数', async ({ page }) => {
await page.goto('/')
const wrap = page.locator('[data-testid="table-wrap"]')
await wrap.scrollIntoViewIfNeeded()
// 单元格数必须与表头列数一致;固定列若被双份渲染,这里会多出来。
const headerCount = await wrap.locator('thead th').count()
const firstRowTd = await wrap.locator('.stk-tbody-main tr').first().locator('td').count()
expect(firstRowTd).toBe(headerCount)
})
test('表头全选:点击后表头与所有数据行的选择列都被勾选', async ({ page }) => {
await page.goto('/')
const wrap = page.locator('[data-testid="table-wrap"]')
await wrap.scrollIntoViewIfNeeded()
const headerCheckbox = wrap.locator('th .g3-table__checkbox').first()
await headerCheckbox.scrollIntoViewIfNeeded()
await headerCheckbox.click()
// 表头 choice 项 + 3 行选择列 = 4 个处于勾选态的选择框
// (「启用」列的 checkbox 是数据值,初始为 1 的那两行也会是勾选态,
// 所以这里按选择列的固定类名限定范围)
await expect(wrap.locator('th .g3-table__checkbox.is-checked')).toHaveCount(1)
await expect(wrap.locator('.stk-tbody-main td:first-child .g3-table__checkbox.is-checked')).toHaveCount(3)
})
})
test.describe('G3Table RTL 镜像', () => {
test('RTL 下固定列与表格布局不塌陷', async ({ page }) => {
await page.goto('/?dir=rtl')
const wrap = page.locator('[data-testid="table-wrap"]')
await wrap.scrollIntoViewIfNeeded()
const wrapBox = await wrap.boundingBox()
const tableBox = await wrap.locator('.stk-table').first().boundingBox()
expect(tableBox).not.toBeNull()
// 表格仍应占据容器的整宽(镜像不该把宽度算丢)
expect(tableBox!.width).toBeGreaterThan(wrapBox!.width * 0.9)
})
})