Files
workspace/code/fms/fms-vue/tests/fms-table-virtual.spec.js
T
2026-08-27 17:32:05 +08:00

183 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { nextTick } from 'vue'
import { registerFeature, useAreaSelection, StkTable } from 'stk-table-vue'
import FmsTable from '@/components/fms-table/FmsTable.vue'
// 与 main.js 一致:注册 stk-table 的按需 feature(行编辑依赖 area-selection)
registerFeature(useAreaSelection)
const COLS = 20
function makeColumns() {
const cols = []
for (let i = 0; i < COLS; i++) {
cols.push({ title: `列 ${i}`, dataIndex: `c${i}`, width: 120, editor: { type: 'input' } })
}
return cols
}
function makeRows(n) {
return Array.from({ length: n }, (_, i) => {
const row = { id: i + 1 }
for (let j = 0; j < COLS; j++) row[`c${j}`] = `${i + 1}-${j}`
return row
})
}
describe('fms-table 虚拟滚动验证', () => {
beforeEach(() => setActivePinia(createPinia()))
it('5000 行时仅渲染可视区行数(虚拟滚动切片生效)', async () => {
const wrapper = mount(FmsTable, {
props: { columns: makeColumns(), dataSource: makeRows(5000), rowKey: 'id', seq: false },
attachTo: document.body,
})
await nextTick()
await new Promise((r) => setTimeout(r, 100))
const trCount = wrapper.findAll('.stk-tbody-main tr').length
console.log('=== 5000 行时 DOM 实际渲染的 <tr> 数量:', trCount)
expect(trCount).toBeGreaterThan(0)
expect(trCount).toBeLessThan(100)
wrapper.unmount()
})
it('20 行时全量渲染(数据量小于阈值关闭虚拟滚动,不再吞掉最后一行)', async () => {
const wrapper = mount(FmsTable, {
props: { columns: makeColumns(), dataSource: makeRows(20), rowKey: 'id', seq: false },
attachTo: document.body,
})
await nextTick()
await new Promise((r) => setTimeout(r, 100))
const trCount = wrapper.findAll('.stk-tbody-main tr').length
console.log('=== 20 行时 DOM 实际渲染的 <tr> 数量:', trCount)
expect(trCount).toBe(20)
wrapper.unmount()
})
it('数据量从 20 行增长到 5000 行后,DOM 行数保持为可视区行数', async () => {
const wrapper = mount(FmsTable, {
props: { columns: makeColumns(), dataSource: makeRows(20), rowKey: 'id', seq: false },
attachTo: document.body,
})
await nextTick()
await new Promise((r) => setTimeout(r, 100))
const before = wrapper.findAll('.stk-tbody-main tr').length
await wrapper.setProps({ dataSource: makeRows(5000) })
await nextTick()
await new Promise((r) => setTimeout(r, 100))
const after = wrapper.findAll('.stk-tbody-main tr').length
console.log(`=== 20 行时渲染 ${before} 行,改为 5000 行后渲染 ${after} 行`)
expect(before).toBeGreaterThan(0)
expect(after).toBeGreaterThan(0)
// 核心断言:5000 行数据时 DOM 只保留可视区行数,不会渲染全部 5000 行
expect(after).toBeLessThan(100)
wrapper.unmount()
})
// 性能优化回归(item #1):非编辑态下可编辑列/纯展示列走 StkTable 原生 td 渲染,
// 不注入 customCell 函数式组件;只有进入编辑后才挂载编辑器。
it('非编辑态:全部单元格走原生 table-cell-wrapper 渲染,零编辑器组件', async () => {
const wrapper = mount(FmsTable, {
props: { columns: makeColumns(), dataSource: makeRows(20), rowKey: 'id', seq: false },
attachTo: document.body,
})
await nextTick()
await new Promise((r) => setTimeout(r, 100))
const tds = wrapper.findAll('.stk-tbody-main td[data-col-key]')
expect(tds.length).toBeGreaterThan(0)
// 没有活动编辑行 → 不注入任何编辑器渲染
expect(wrapper.find('.fms-cell-editor-root').exists()).toBe(false)
// 每个数据单元格都走库原生 table-cell-wrapper 路径(无 fms-cell-display 包裹)
const nativeWrappers = wrapper.findAll('.stk-tbody-main .table-cell-wrapper')
expect(nativeWrappers.length).toBe(tds.length)
wrapper.unmount()
})
// 性能优化回归(item #1):进入编辑后仅活动编辑行挂载编辑器,其余行保持原生渲染
it('进入编辑后:仅活动编辑行挂载编辑器,其余行保持原生渲染', async () => {
const wrapper = mount(FmsTable, {
props: { columns: makeColumns(), dataSource: makeRows(20), rowKey: 'id', seq: false },
attachTo: document.body,
})
await nextTick()
await new Promise((r) => setTimeout(r, 100))
const trs = wrapper.findAll('.stk-tbody-main tr')
const td = trs[1].find('td[data-col-key]')
await td.trigger('mousedown', { button: 0 })
await nextTick()
await td.trigger('mouseup')
await nextTick()
await new Promise((r) => setTimeout(r, 100))
const editingRow = wrapper.findAll('.stk-tbody-main tr')[1]
const editorCountInEditingRow = editingRow.findAll('.fms-cell-editor-root').length
expect(editorCountInEditingRow).toBeGreaterThan(0)
expect(editorCountInEditingRow).toBeLessThanOrEqual(makeColumns().length)
// 其余行不挂载编辑器(保持原生渲染,滚动时行间切换不重建整表)
trs.forEach((tr, index) => {
if (index === 1) return
expect(tr.findAll('.fms-cell-editor-root').length).toBe(0)
})
wrapper.unmount()
})
it('点击单元格进入单行编辑(area-selection 注册后生效,编辑器渲染)', async () => {
const wrapper = mount(FmsTable, {
props: { columns: makeColumns(), dataSource: makeRows(20), rowKey: 'id', seq: false },
attachTo: document.body,
})
await nextTick()
await new Promise((r) => setTimeout(r, 100))
// 点击第 2 行第 1 个数据列(跳过 virtualX 的占位 td,选中带 data-col-key 的单元格)
const trs = wrapper.findAll('.stk-tbody-main tr')
const td = trs[1].find('td[data-col-key]')
console.log('=== td outerHTML:', td.element.outerHTML.slice(0, 200))
await td.trigger('mousedown', { button: 0 })
await nextTick()
console.log('=== mousedown 后 td data-cs-s:', td.attributes()['data-cs-s'])
await td.trigger('mouseup')
await nextTick()
await new Promise((r) => setTimeout(r, 100))
const editor = wrapper.find('.fms-cell-editor-root')
console.log('=== 点击后编辑器是否渲染:', editor.exists())
expect(editor.exists()).toBe(true)
wrapper.unmount()
})
// 列宽拖拽回归:库 useColResize 在 mouseUp 时「原地改写」传入的列对象 width 并
// emit update:columns(同一批对象的新数组)。未绑定 v-model:columns 的父组件
// 不会回灌新数组,displayColumns 必须自行换新数组引用,库才能重算出新列宽。
// 复现方式:拿到库实际收到的 displayColumns,原地改第 1 列宽度后以同批对象
// emit update:columns,断言表头渲染出新宽度。
it('拖拽列宽后(未绑定 v-model:columns),新列宽立即生效不丢失', async () => {
const columns = makeColumns()
const wrapper = mount(FmsTable, {
props: { columns, dataSource: makeRows(20), rowKey: 'id', seq: false },
attachTo: document.body,
})
await nextTick()
await new Promise((r) => setTimeout(r, 100))
const stkTable = wrapper.findComponent(StkTable)
const displayColumns = stkTable.props('columns')
const target = displayColumns.find((c) => c.dataIndex === 'c1')
target.width = '333px' // 模拟 useColResize mouseUp 的原地改写
await stkTable.vm.$emit('update:columns', displayColumns.slice())
await nextTick()
await new Promise((r) => setTimeout(r, 100))
// 表头 th 宽度经 --cw CSS 变量渲染;virtualX 模式下取自 __W__(dealColumns 重算产物)
const th = wrapper.findAll('thead th').find((el) => el.attributes('data-col-key') === 'c1')
expect(th.attributes('style')).toContain('333px')
wrapper.unmount()
})
})