183 lines
7.8 KiB
JavaScript
183 lines
7.8 KiB
JavaScript
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()
|
||
})
|
||
})
|