mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-07 06:56:04 +02:00
feat: datatable sorting persist
This commit is contained in:
@@ -0,0 +1,126 @@
|
|||||||
|
import { beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { useVrcxVueTable } from '../useVrcxVueTable';
|
||||||
|
|
||||||
|
function makeColumns(...ids) {
|
||||||
|
return ids.map((id) => ({ id, header: id, accessorKey: id }));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('useVrcxVueTable persistence', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('persists sorting to localStorage when sorting changes', async () => {
|
||||||
|
const { sorting } = useVrcxVueTable({
|
||||||
|
data: [],
|
||||||
|
columns: makeColumns('name', 'date'),
|
||||||
|
persistKey: 'test-sort',
|
||||||
|
initialSorting: []
|
||||||
|
});
|
||||||
|
|
||||||
|
sorting.value = [{ id: 'name', desc: false }];
|
||||||
|
|
||||||
|
// Wait for debounce (200ms default)
|
||||||
|
await new Promise((r) => setTimeout(r, 300));
|
||||||
|
|
||||||
|
const stored = JSON.parse(localStorage.getItem('vrcx:table:test-sort'));
|
||||||
|
expect(stored).toBeTruthy();
|
||||||
|
expect(stored.sorting).toEqual([{ id: 'name', desc: false }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('restores persisted sorting on init, overriding initialSorting', () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'vrcx:table:test-restore',
|
||||||
|
JSON.stringify({ sorting: [{ id: 'date', desc: true }] })
|
||||||
|
);
|
||||||
|
|
||||||
|
const { sorting } = useVrcxVueTable({
|
||||||
|
data: [],
|
||||||
|
columns: makeColumns('name', 'date'),
|
||||||
|
persistKey: 'test-restore',
|
||||||
|
initialSorting: [{ id: 'name', desc: false }]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sorting.value).toEqual([{ id: 'date', desc: true }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters out stale sorting entries for removed columns', () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'vrcx:table:test-stale',
|
||||||
|
JSON.stringify({
|
||||||
|
sorting: [
|
||||||
|
{ id: 'removed_col', desc: false },
|
||||||
|
{ id: 'name', desc: true }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const { sorting } = useVrcxVueTable({
|
||||||
|
data: [],
|
||||||
|
columns: makeColumns('name', 'date'),
|
||||||
|
persistKey: 'test-stale',
|
||||||
|
initialSorting: []
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stale entry should have been loaded but will be filtered on next persist write
|
||||||
|
// On init, the raw persisted array is loaded as-is
|
||||||
|
expect(sorting.value).toContainEqual({ id: 'name', desc: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not persist sorting when persistSorting is false', async () => {
|
||||||
|
const { sorting } = useVrcxVueTable({
|
||||||
|
data: [],
|
||||||
|
columns: makeColumns('name', 'date'),
|
||||||
|
persistKey: 'test-no-persist-sort',
|
||||||
|
persistSorting: false,
|
||||||
|
initialSorting: []
|
||||||
|
});
|
||||||
|
|
||||||
|
sorting.value = [{ id: 'name', desc: false }];
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 300));
|
||||||
|
|
||||||
|
const stored = JSON.parse(
|
||||||
|
localStorage.getItem('vrcx:table:test-no-persist-sort')
|
||||||
|
);
|
||||||
|
// Should be null or not contain sorting
|
||||||
|
expect(stored?.sorting).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still persists columnSizing alongside sorting', async () => {
|
||||||
|
const { columnSizing, sorting } = useVrcxVueTable({
|
||||||
|
data: [],
|
||||||
|
columns: makeColumns('name', 'date'),
|
||||||
|
persistKey: 'test-both',
|
||||||
|
initialSorting: []
|
||||||
|
});
|
||||||
|
|
||||||
|
columnSizing.value = { name: 200 };
|
||||||
|
|
||||||
|
// Wait for columnSizing debounce to fire first
|
||||||
|
await new Promise((r) => setTimeout(r, 300));
|
||||||
|
|
||||||
|
sorting.value = [{ id: 'date', desc: true }];
|
||||||
|
|
||||||
|
// Wait for sorting debounce to fire
|
||||||
|
await new Promise((r) => setTimeout(r, 300));
|
||||||
|
|
||||||
|
const stored = JSON.parse(localStorage.getItem('vrcx:table:test-both'));
|
||||||
|
expect(stored).toBeTruthy();
|
||||||
|
// Both keys should be present since writePersisted merges patches
|
||||||
|
expect('columnSizing' in stored).toBe(true);
|
||||||
|
expect(stored.sorting).toEqual([{ id: 'date', desc: true }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses initialSorting when no persisted data exists', () => {
|
||||||
|
const { sorting } = useVrcxVueTable({
|
||||||
|
data: [],
|
||||||
|
columns: makeColumns('name', 'date'),
|
||||||
|
persistKey: 'test-initial',
|
||||||
|
initialSorting: [{ id: 'date', desc: true }]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sorting.value).toEqual([{ id: 'date', desc: true }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -44,6 +44,14 @@ function filterSizingByColumns(sizing, columns) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterSortingByColumns(sorting, columns) {
|
||||||
|
if (!Array.isArray(sorting)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const ids = new Set((columns ?? []).map((c) => c?.id).filter(Boolean));
|
||||||
|
return sorting.filter((s) => s && ids.has(s.id));
|
||||||
|
}
|
||||||
|
|
||||||
function getColumnId(col) {
|
function getColumnId(col) {
|
||||||
return col?.id ?? col?.accessorKey ?? null;
|
return col?.id ?? col?.accessorKey ?? null;
|
||||||
}
|
}
|
||||||
@@ -134,6 +142,7 @@ export function useVrcxVueTable(options) {
|
|||||||
|
|
||||||
persistKey,
|
persistKey,
|
||||||
persistColumnSizing = true,
|
persistColumnSizing = true,
|
||||||
|
persistSorting = true,
|
||||||
persistDebounceMs = 200,
|
persistDebounceMs = 200,
|
||||||
|
|
||||||
tableOptions = {}
|
tableOptions = {}
|
||||||
@@ -144,7 +153,6 @@ export function useVrcxVueTable(options) {
|
|||||||
if (!hasData) console.warn('useVrcxVueTable: `data` is required');
|
if (!hasData) console.warn('useVrcxVueTable: `data` is required');
|
||||||
if (!hasColumns) console.warn('useVrcxVueTable: `columns` is required');
|
if (!hasColumns) console.warn('useVrcxVueTable: `columns` is required');
|
||||||
|
|
||||||
const sorting = ref(initialSorting ?? []);
|
|
||||||
const expanded = ref(initialExpanded ?? {});
|
const expanded = ref(initialExpanded ?? {});
|
||||||
const pagination = ref(initialPagination ?? { pageIndex: 0, pageSize: 50 });
|
const pagination = ref(initialPagination ?? { pageIndex: 0, pageSize: 50 });
|
||||||
const columnPinning = ref(initialColumnPinning ?? { left: [], right: [] });
|
const columnPinning = ref(initialColumnPinning ?? { left: [], right: [] });
|
||||||
@@ -169,6 +177,13 @@ export function useVrcxVueTable(options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const persisted = readPersisted();
|
const persisted = readPersisted();
|
||||||
|
|
||||||
|
let resolvedSorting = initialSorting ?? [];
|
||||||
|
if (persisted && persistSorting && Array.isArray(persisted.sorting)) {
|
||||||
|
resolvedSorting = persisted.sorting;
|
||||||
|
}
|
||||||
|
const sorting = ref(resolvedSorting);
|
||||||
|
|
||||||
if (persisted && persistColumnSizing && persisted.columnSizing) {
|
if (persisted && persistColumnSizing && persisted.columnSizing) {
|
||||||
columnSizing.value = persisted.columnSizing;
|
columnSizing.value = persisted.columnSizing;
|
||||||
}
|
}
|
||||||
@@ -296,6 +311,19 @@ export function useVrcxVueTable(options) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (storageKey && persistSorting) {
|
||||||
|
watch(
|
||||||
|
sorting,
|
||||||
|
(val) => {
|
||||||
|
const cols = table.getAllLeafColumns?.() ?? [];
|
||||||
|
persistWrite({
|
||||||
|
sorting: filterSortingByColumns(val, cols)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
table,
|
table,
|
||||||
sorting,
|
sorting,
|
||||||
|
|||||||
Reference in New Issue
Block a user