252 lines
10 KiB
JavaScript
252 lines
10 KiB
JavaScript
import {
|
||
createTable,
|
||
filterPlugin,
|
||
paginationPlugin,
|
||
selectionPlugin,
|
||
sortPlugin,
|
||
} from '../dist/index.js';
|
||
|
||
const users = [
|
||
{ id: 1, name: 'Charlie', age: 35, city: 'Shenzhen' },
|
||
{ id: 2, name: 'Alice', age: 28, city: 'Beijing' },
|
||
{ id: 3, name: 'Bob', age: 31, city: 'Shanghai' },
|
||
{ id: 4, name: 'Dave', age: 22, city: 'Beijing' },
|
||
{ id: 5, name: 'Eve', age: 29, city: 'Shanghai' },
|
||
];
|
||
|
||
const table = createTable({
|
||
data: users,
|
||
columns: [
|
||
{ key: 'name', header: 'Name', sortable: true },
|
||
{ key: 'age', header: 'Age', sortable: true },
|
||
{ key: 'city', header: 'City' },
|
||
],
|
||
rowKey: 'id',
|
||
plugins: [filterPlugin(), sortPlugin(), paginationPlugin(), selectionPlugin()],
|
||
initialState: { pagination: { page: 1, pageSize: 2 } },
|
||
});
|
||
|
||
/*** 断言工具:失败即退出 ***/
|
||
const assert = (cond, msg) => {
|
||
if (!cond) {
|
||
console.error('FAIL:', msg);
|
||
process.exit(1);
|
||
}
|
||
console.log('ok -', msg);
|
||
};
|
||
|
||
table.dispatch({ type: 'toggleSort', key: 'name' }); // 按名称升序
|
||
assert(
|
||
JSON.stringify(table.getVisibleRows().rows.map((r) => r.name)) ===
|
||
JSON.stringify(['Alice', 'Bob']),
|
||
'sort by name asc + page 1 of size 2',
|
||
);
|
||
|
||
table.dispatch({ type: 'setFilter', key: 'city', value: 'shang' }); // 筛选上海
|
||
const filtered = table.getVisibleRows();
|
||
assert(
|
||
JSON.stringify(filtered.rows.map((r) => r.name)) ===
|
||
JSON.stringify(['Bob', 'Eve']) &&
|
||
filtered.total === 2,
|
||
'filter city~sh keeps Shanghai rows, total=2',
|
||
);
|
||
assert(table.getPageInfo().totalPages === 1, 'totalPages computed from filtered total');
|
||
|
||
table.dispatch({ type: 'clearFilters' }); // 清筛选回到全量
|
||
table.dispatch({ type: 'setPage', page: 2 });
|
||
assert(
|
||
JSON.stringify(table.getVisibleRows().rows.map((r) => r.name)) ===
|
||
JSON.stringify(['Charlie', 'Dave']),
|
||
'page 2 after clearing filter',
|
||
);
|
||
assert(table.getPageInfo().page === 2 && table.getPageInfo().totalPages === 3, 'page info total=3 pages');
|
||
|
||
table.dispatch({ type: 'toggleSelect', key: 3 }); // 勾选行 3
|
||
assert(JSON.stringify(table.getState().selection) === JSON.stringify([3]), 'toggleSelect adds rowKey');
|
||
table.selectAllVisible(); // 全选当前页(替换语义)
|
||
assert(
|
||
JSON.stringify(table.getState().selection) === JSON.stringify([1, 4]),
|
||
'selectAllVisible replaces selection with visible rowKeys',
|
||
);
|
||
|
||
table.dispatch({ type: 'setPage', page: 99 }); // 越界页码
|
||
const clamped = table.getVisibleRows();
|
||
assert(
|
||
table.getPageInfo().page === 3 && clamped.rows.length === 1,
|
||
'page clamped to totalPages',
|
||
);
|
||
|
||
const before = table.getVisibleRows(); // memo 基线
|
||
table.dispatch({ type: 'clearSelection' }); // 无关状态变更
|
||
assert(table.getVisibleRows() === before, 'derive stages memoized across unrelated state changes');
|
||
|
||
// —— 补充不变量 ——
|
||
const fresh = createTable({
|
||
data: users,
|
||
columns: [{ key: 'name', header: 'Name', sortable: true }],
|
||
rowKey: 'id',
|
||
plugins: [paginationPlugin()],
|
||
initialState: { pagination: { page: 2, pageSize: 2 } },
|
||
});
|
||
assert(
|
||
fresh.getPageInfo().page === 2 && fresh.getPageInfo().pageSize === 2,
|
||
'getPageInfo 无需先取可见行即返回 initialState 生效值',
|
||
);
|
||
assert(fresh.getPageInfo().totalPages === 3, 'getPageInfo 的 totalPages 由上游 total 推导');
|
||
|
||
const marks = createTable({
|
||
data: users,
|
||
columns: [{ key: 'name', header: 'Name' }, { key: 'city', header: 'City' }],
|
||
rowKey: 'id',
|
||
plugins: [filterPlugin(), paginationPlugin(), selectionPlugin()],
|
||
initialState: { pagination: { page: 1, pageSize: 2 } },
|
||
});
|
||
marks.selectAllVisible(); // 选中第 1 页的 id 1 / 2
|
||
marks.dispatch({ type: 'setFilter', key: 'city', value: 'Beijing' }); // 可见行整体换掉
|
||
assert(
|
||
JSON.stringify(marks.getState().selection) === JSON.stringify([1, 2]),
|
||
'selection 以 rowKey 记账,筛选换掉可见行后仍保留',
|
||
);
|
||
|
||
const toggle = createTable({
|
||
data: users,
|
||
columns: [{ key: 'age', header: 'Age', sortable: true }],
|
||
rowKey: 'id',
|
||
plugins: [sortPlugin()],
|
||
});
|
||
toggle.dispatch({ type: 'toggleSort', key: 'age' });
|
||
toggle.dispatch({ type: 'toggleSort', key: 'age' });
|
||
toggle.dispatch({ type: 'toggleSort', key: 'age' });
|
||
assert(toggle.getState().sorting.length === 0, '同一列连点三次回到无序(asc → desc → 取消)');
|
||
|
||
const multi = createTable({
|
||
data: users,
|
||
columns: [{ key: 'city', header: 'City' }, { key: 'age', header: 'Age', sortable: true }],
|
||
rowKey: 'id',
|
||
plugins: [sortPlugin({ multi: true })],
|
||
});
|
||
multi.dispatch({ type: 'toggleSort', key: 'city', multi: true });
|
||
multi.dispatch({ type: 'toggleSort', key: 'age', multi: true });
|
||
assert(multi.getState().sorting.length === 2, 'multi 排序保留两列规则');
|
||
assert(
|
||
JSON.stringify(multi.getVisibleRows().rows.map((r) => r.id)) === JSON.stringify([4, 2, 5, 3, 1]),
|
||
'多列排序按规则顺序生效(city 升序,同城内 age 升序:Beijing 4→2、Shanghai 5→3)',
|
||
);
|
||
|
||
const sizing = createTable({
|
||
data: users,
|
||
columns: [{ key: 'name', header: 'Name' }],
|
||
rowKey: 'id',
|
||
plugins: [paginationPlugin()],
|
||
initialState: { pagination: { page: 1, pageSize: 2 } },
|
||
});
|
||
sizing.dispatch({ type: 'setPage', page: 2 });
|
||
sizing.dispatch({ type: 'setPageSize', pageSize: 3 });
|
||
assert(sizing.getPageInfo().page === 1 && sizing.getPageInfo().pageSize === 3, '改页大小回到第 1 页');
|
||
|
||
sizing.dispatch({ type: 'toggleSort', key: 'name' });
|
||
sizing.dispatch({ type: 'reset' });
|
||
assert(
|
||
sizing.getState().sorting.length === 0 &&
|
||
sizing.getState().filters.length === 0 &&
|
||
sizing.getState().pagination.page === 1 &&
|
||
sizing.getState().pagination.pageSize === 20,
|
||
'reset 回到默认态(排序/筛选清空,分页回 1/20)',
|
||
);
|
||
|
||
// getAllRows:过筛选 + 排序但跳过分页,供导出用
|
||
const all = createTable({
|
||
data: users,
|
||
columns: [{ key: 'name', header: 'Name' }, { key: 'city', header: 'City' }],
|
||
rowKey: 'id',
|
||
plugins: [filterPlugin(), sortPlugin(), paginationPlugin()],
|
||
initialState: { pagination: { page: 1, pageSize: 2 } },
|
||
});
|
||
assert(all.getVisibleRows().rows.length === 2, 'getVisibleRows 受分页约束只给当页');
|
||
all.dispatch({ type: 'setFilter', key: 'city', value: 'Beijing' });
|
||
assert(all.getAllRows().rows.length === 2 && all.getVisibleRows().rows.length === 2, 'getAllRows 跳过分页但与筛选一致(Beijing 2 行)');
|
||
all.dispatch({ type: 'toggleSort', key: 'age' });
|
||
assert(
|
||
JSON.stringify(all.getAllRows().rows.map((r) => r.id)) === JSON.stringify([4, 2]),
|
||
'getAllRows 仍按排序规则排(age 升序 4→2)',
|
||
);
|
||
assert(all.getPageInfo().totalPages === 1, 'getAllRows 不影响分页快照(2 行 / 每页 2 = 1 页)');
|
||
|
||
// setRows:整体换数据源,选择状态按 rowKey 剔除失效行,页码越界由插件收敛
|
||
const swapped = createTable({
|
||
data: users,
|
||
columns: [{ key: 'name', header: 'Name' }],
|
||
rowKey: 'id',
|
||
plugins: [selectionPlugin(), paginationPlugin()],
|
||
initialState: { pagination: { page: 1, pageSize: 2 } },
|
||
});
|
||
swapped.dispatch({ type: 'toggleSelect', key: 1 }); // 选中两行,后面验证失效行被剔除
|
||
swapped.dispatch({ type: 'toggleSelect', key: 4 });
|
||
assert(swapped.isRowSelected(1) && swapped.isRowSelected(4), 'setRows 前:两行已选');
|
||
swapped.setRows(users.filter((u) => u.id !== 4));
|
||
assert(swapped.getVisibleRows().total === 4, 'setRows 后:total 跟随新数据(5 行 → 4 行)');
|
||
assert(swapped.isRowSelected(1) && !swapped.isRowSelected(4), 'setRows 后:失效行键从选择里剔除,有效行保留');
|
||
assert(swapped.getState().pagination.page === 1, 'setRows 不动页码(用户翻页意图不被打断)');
|
||
const sameRows = users.filter((u) => u.id !== 4);
|
||
let notified = 0;
|
||
swapped.subscribe(() => (notified += 1));
|
||
swapped.setRows(sameRows);
|
||
assert(notified === 1 && swapped.getVisibleRows().total === 4, 'setRows 换引用:通知一次并重算 total');
|
||
|
||
// 页码收敛:数据缩水后越界页码范围回生效页,state 与视图保持一致(不再滞留旧页码)
|
||
const clamp = createTable({
|
||
data: users,
|
||
columns: [{ key: 'name', header: 'Name' }],
|
||
rowKey: 'id',
|
||
plugins: [paginationPlugin()],
|
||
initialState: { pagination: { page: 3, pageSize: 2 } },
|
||
});
|
||
assert(clamp.getVisibleRows().rows.length === 1 && clamp.getPageInfo().page === 3, '收敛前:5 行 / 每页 2 = 3 页,停在第 3 页');
|
||
clamp.setRows(users.slice(0, 1)); // 只剩 1 行 → 总页数 1
|
||
assert(clamp.getPageInfo().page === 1 && clamp.getState().pagination.page === 1, '数据缩水后:越界页码回写为 1(state 与生效页一致)');
|
||
clamp.setRows(users); // 数据恢复 5 行
|
||
assert(clamp.getVisibleRows().rows.length === 2 && clamp.getState().pagination.page === 1, '数据恢复后:仍停第 1 页,不记忆已失效的旧页码');
|
||
clamp.dispatch({ type: 'setPage', page: 9 }); // 显式跳到越界页
|
||
assert(clamp.getVisibleRows().rows.length === 1 && clamp.getState().pagination.page === 3, '跳越界页:派生钳制后回写为末页 3');
|
||
assert(clamp.getPageInfo().totalPages === 3, '收敛不改分页快照语义(totalPages 仍按 total 推算)');
|
||
|
||
// setColumns:动态换列,失效的排序 / 筛选按 key 剔除,accessor 跟随新列集
|
||
const colsTable = createTable({
|
||
data: users,
|
||
columns: [
|
||
{ key: 'name', header: 'Name', sortable: true },
|
||
{ key: 'age', header: 'Age', sortable: true, accessor: (row) => row.age },
|
||
{ key: 'city', header: 'City' },
|
||
],
|
||
rowKey: 'id',
|
||
plugins: [filterPlugin(), sortPlugin(), paginationPlugin(), selectionPlugin()],
|
||
initialState: {
|
||
sorting: [{ key: 'age', dir: 'desc' }],
|
||
filters: [{ key: 'city', value: 'Beijing' }],
|
||
},
|
||
});
|
||
assert(colsTable.getVisibleRows().total === 2, 'setColumns 前:city 筛选生效(Beijing 2 行)');
|
||
colsTable.setColumns([
|
||
{ key: 'name', header: 'Name', sortable: true },
|
||
{ key: 'score', header: 'Score', accessor: (row) => 100 - row.age }, // 换列集:city 列没了,age 列换成派生 score
|
||
]);
|
||
assert(
|
||
colsTable.getState().sorting.length === 0 && colsTable.getState().filters.length === 0,
|
||
'setColumns 后:指向不存在列的排序 / 筛选整体剔除(age 列已换成 score)',
|
||
);
|
||
assert(colsTable.getVisibleRows().total === 5, 'setColumns 后:失效筛选被剔除,total 回到全量 5 行');
|
||
assert(colsTable.getColumns().length === 2 && colsTable.getColumns()[1].key === 'score', 'setColumns 后:getColumns 返回新列集');
|
||
assert(
|
||
colsTable.getCellValue(users[0], 'score') === 65,
|
||
'setColumns 后:getCellValue 跟随新列 accessor(score = 100 - age)',
|
||
);
|
||
const sameCols = colsTable.getColumns();
|
||
let colNotified = 0;
|
||
colsTable.subscribe(() => {
|
||
colNotified += 1;
|
||
});
|
||
colsTable.setColumns(sameCols);
|
||
assert(colNotified === 0, 'setColumns 引用未变:短路不通知');
|
||
|
||
console.log('SMOKE OK');
|