This commit is contained in:
2026-09-30 14:01:58 +08:00
parent f14e1a2ea3
commit 8c51e9c1cf
109 changed files with 1525 additions and 651 deletions
@@ -10,6 +10,26 @@ import {
describe('UI layout preferences', () => {
beforeEach(() => localStorage.clear())
it('restores only finite valid widths for current table columns', () => {
localStorage.setItem(preferenceStorageKey('table', 'user-1', 'orders'), JSON.stringify({
version: 1, order: ['name', 'status'], hidden: [],
widths: { name: 215.6, status: '200', small: 29, negative: -10, missing: 150, nullWidth: null },
}))
expect(readLayoutPreference('table', 'user-1', 'orders', ['name', 'status', 'small', 'negative', 'nullWidth']).widths).toEqual({ name: 216 })
expect(readLayoutPreference('table', 'user-2', 'orders', ['name']).widths).toBeUndefined()
expect(readLayoutPreference('table', 'user-1', 'other-orders', ['name']).widths).toBeUndefined()
})
it('ignores malformed width maps without losing valid order and visibility', () => {
for (const widths of [null, 'bad', [150, 200]]) {
localStorage.setItem(preferenceStorageKey('table', 'user-1', 'orders'), JSON.stringify({
version: 1, order: ['status', 'name'], hidden: ['name'], widths,
}))
expect(readLayoutPreference('table', 'user-1', 'orders', ['name', 'status']))
.toEqual({ version: 1, order: ['status', 'name'], hidden: ['name'], widths: {} })
}
})
it('reconciles stored order with the current stable keys', () => {
expect(reconcileOrder(['actions', 'name', 'status', 'createdAt'], ['status', 'removed', 'name', 'status']))
.toEqual(['status', 'name', 'actions', 'createdAt'])
+16
View File
@@ -6,6 +6,21 @@ export interface LayoutPreferenceSnapshot {
version: 1
order: string[]
hidden: string[]
widths?: Record<string, number>
}
export function normalizeColumnWidth(value: unknown): number | undefined {
return typeof value === 'number' && Number.isFinite(value) && value >= 30
? Math.round(value)
: undefined
}
function reconcileWidths(value: unknown, availableKeys: readonly string[]) {
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
return Object.fromEntries(Object.entries(value).flatMap(([key, width]) => {
const normalized = normalizeColumnWidth(width)
return availableKeys.includes(key) && normalized !== undefined ? [[key, normalized]] : []
}))
}
export const UI_PREFERENCE_SCOPE: InjectionKey<ComputedRef<string>> = Symbol('ui-preference-scope')
@@ -53,6 +68,7 @@ export function readLayoutPreference(
version: 1,
order: reconcileOrder(availableKeys, parsed.order),
hidden: uniqueKeys([...parsed.hidden, ...newlyDefaultHidden]).filter((key) => availableKeys.includes(key)),
...(kind === 'table' && parsed.widths !== undefined ? { widths: reconcileWidths(parsed.widths, availableKeys) } : {}),
}
} catch {
return fallback