点击工具栏打开可筛选、复制的只读弹层,替换原行列点选复制模式,且不写回业务数据。 Co-authored-by: Cursor <cursoragent@cursor.com>
182 lines
5.7 KiB
TypeScript
182 lines
5.7 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { describe, it } from 'node:test'
|
|
import {
|
|
buildExcelSnapshot,
|
|
buildExcelTsv,
|
|
excelCell,
|
|
isCopyableColumn,
|
|
isRowSelectorColumn,
|
|
resolveCopyAxes,
|
|
snapshotToUniverCellData,
|
|
toggleAxis
|
|
} from './list-excel-copy'
|
|
|
|
describe('isCopyableColumn', () => {
|
|
it('keeps ordinary data columns', () => {
|
|
assert.equal(isCopyableColumn({ id: 'name', label: '联系人' }), true)
|
|
})
|
|
|
|
it('drops selection, index, expand, label 操作, and className containing col-actions', () => {
|
|
assert.equal(isCopyableColumn({ id: 's', label: '', type: 'selection' }), false)
|
|
assert.equal(isCopyableColumn({ id: 'i', label: '', type: 'index' }), false)
|
|
assert.equal(isCopyableColumn({ id: 'e', label: '', type: 'expand' }), false)
|
|
assert.equal(isCopyableColumn({ id: 'a', label: '操作' }), false)
|
|
assert.equal(
|
|
isCopyableColumn({ id: 'act', label: '编辑', className: 'col-actions fixed' }),
|
|
false
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('isRowSelectorColumn', () => {
|
|
it('treats index type and 序号/# labels as the row head', () => {
|
|
assert.equal(isRowSelectorColumn({ id: 'i', label: '', type: 'index' }), true)
|
|
assert.equal(isRowSelectorColumn({ id: 'i', label: '序号' }), true)
|
|
assert.equal(isRowSelectorColumn({ id: 'i', label: '#' }), true)
|
|
assert.equal(isRowSelectorColumn({ id: 'n', label: '客户名称' }), false)
|
|
})
|
|
})
|
|
|
|
describe('excelCell', () => {
|
|
it('trims and turns tabs and line breaks into spaces', () => {
|
|
assert.equal(excelCell(' 合作中 \n'), '合作中')
|
|
assert.equal(excelCell('重庆\t长寿\r\n区'), '重庆 长寿 区')
|
|
assert.equal(excelCell('重庆\r长寿'), '重庆 长寿')
|
|
})
|
|
})
|
|
|
|
describe('toggleAxis', () => {
|
|
it('toggles one index and remembers anchor; second toggle of same index removes it but keeps anchor', () => {
|
|
let selected = new Set<number>()
|
|
let anchor: number | null = null
|
|
|
|
const first = toggleAxis(selected, 2, false, anchor)
|
|
selected = first.selected
|
|
anchor = first.anchor
|
|
assert.deepEqual([...selected].sort(), [2])
|
|
assert.equal(anchor, 2)
|
|
|
|
const second = toggleAxis(selected, 2, false, anchor)
|
|
selected = second.selected
|
|
anchor = second.anchor
|
|
assert.deepEqual([...selected], [])
|
|
assert.equal(anchor, 2)
|
|
})
|
|
|
|
it('shift-selects inclusive span and keeps earlier picks: start with set containing 0 and anchor 0, shift-click 3 yields 0,1,2,3 and anchor 3', () => {
|
|
const selected = new Set([0])
|
|
const result = toggleAxis(selected, 3, true, 0)
|
|
assert.deepEqual(
|
|
[...result.selected].sort((a, b) => a - b),
|
|
[0, 1, 2, 3]
|
|
)
|
|
assert.equal(result.anchor, 3)
|
|
})
|
|
|
|
it('shift without an anchor toggles the clicked index', () => {
|
|
const selected = new Set<number>()
|
|
const result = toggleAxis(selected, 1, true, null)
|
|
assert.deepEqual([...result.selected], [1])
|
|
assert.equal(result.anchor, 1)
|
|
})
|
|
})
|
|
|
|
describe('resolveCopyAxes', () => {
|
|
it('returns null when nothing selected', () => {
|
|
assert.equal(
|
|
resolveCopyAxes({
|
|
selectedCols: [],
|
|
selectedRows: [],
|
|
columnCount: 3,
|
|
rowCount: 2
|
|
}),
|
|
null
|
|
)
|
|
})
|
|
|
|
it('expands columns to every current row, sorting cols: selectedCols [2,0], rowCount 2, columnCount 3 => cols [0,2] rows [0,1]', () => {
|
|
const result = resolveCopyAxes({
|
|
selectedCols: [2, 0],
|
|
selectedRows: [],
|
|
columnCount: 3,
|
|
rowCount: 2
|
|
})
|
|
assert.deepEqual(result, { cols: [0, 2], rows: [0, 1] })
|
|
})
|
|
|
|
it('expands rows to every copyable column: selectedRows [3,1], columnCount 2, rowCount 4 => cols [0,1] rows [1,3]', () => {
|
|
const result = resolveCopyAxes({
|
|
selectedCols: [],
|
|
selectedRows: [3, 1],
|
|
columnCount: 2,
|
|
rowCount: 4
|
|
})
|
|
assert.deepEqual(result, { cols: [0, 1], rows: [1, 3] })
|
|
})
|
|
|
|
it('intersection when both selected: selectedCols [1], selectedRows [0,2], columnCount 3, rowCount 4 => cols [1] rows [0,2]', () => {
|
|
const result = resolveCopyAxes({
|
|
selectedCols: [1],
|
|
selectedRows: [0, 2],
|
|
columnCount: 3,
|
|
rowCount: 4
|
|
})
|
|
assert.deepEqual(result, { cols: [1], rows: [0, 2] })
|
|
})
|
|
})
|
|
|
|
describe('buildExcelTsv', () => {
|
|
it('builds CRLF-separated TSV with header row', () => {
|
|
const tsv = buildExcelTsv(
|
|
['联系人', '状态'],
|
|
[
|
|
['张三', '合作中'],
|
|
['李四', '跟进中']
|
|
]
|
|
)
|
|
assert.equal(tsv, '联系人\t状态\r\n张三\t合作中\r\n李四\t跟进中')
|
|
})
|
|
})
|
|
|
|
describe('buildExcelSnapshot', () => {
|
|
it('keeps copyable columns, cleans cells, and drops 操作', () => {
|
|
const snapshot = buildExcelSnapshot({
|
|
columns: [
|
|
{ id: 'name', label: ' 联系人\n' },
|
|
{ id: 'act', label: '操作' },
|
|
{ id: 'status', label: '状态' }
|
|
],
|
|
rowValues: [
|
|
['张三\t甲', '编辑', '合作中'],
|
|
['李四', '删除', '跟进中']
|
|
]
|
|
})
|
|
assert.deepEqual(snapshot.headers, ['联系人', '状态'])
|
|
assert.deepEqual(snapshot.rows, [
|
|
['张三 甲', '合作中'],
|
|
['李四', '跟进中']
|
|
])
|
|
})
|
|
|
|
it('returns empty snapshot when no copyable columns', () => {
|
|
const snapshot = buildExcelSnapshot({
|
|
columns: [{ id: 'a', label: '操作' }],
|
|
rowValues: [['x']]
|
|
})
|
|
assert.deepEqual(snapshot, { headers: [], rows: [] })
|
|
})
|
|
})
|
|
|
|
describe('snapshotToUniverCellData', () => {
|
|
it('puts headers on row 0 and data from row 1', () => {
|
|
const cellData = snapshotToUniverCellData({
|
|
headers: ['A', 'B'],
|
|
rows: [['1', '2']]
|
|
})
|
|
assert.deepEqual(cellData[0][0], { v: 'A', t: 1 })
|
|
assert.deepEqual(cellData[0][1], { v: 'B', t: 1 })
|
|
assert.deepEqual(cellData[1][0], { v: '1', t: 1 })
|
|
assert.deepEqual(cellData[1][1], { v: '2', t: 1 })
|
|
})
|
|
})
|