Files
EasyCLIProxyAPI/tests/usagePreferences.test.ts
lzt404 8d3912f81b fix(usage): persist view preferences across updates
- Save pagination, visible columns, and column widths to the app data directory
- Migrate existing localStorage preferences and restore them on startup
- Preserve user choices instead of applying new defaults during upgrades
- Add regression tests for preference recovery and update preservation
2026-10-06 13:41:35 +08:00

90 lines
4.9 KiB
TypeScript

import { describe, expect, it } from 'bun:test';
import { UsagePreferences } from '../src/services/usagePreferences';
import { getInitialVisibleColumns, getInitialColumnWidths } from '../src/pages/UsageEventsView';
const pageSize = 'cpa-gui.usage-events-page-size.v1';
const columns = 'cpa-gui.usage-events-visible-cols.v6';
function storage(initial: Record<string, string> = {}): Storage {
const values = new Map(Object.entries(initial));
return {
get length() { return values.size; },
key: (index) => [...values.keys()][index] ?? null,
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => { values.set(key, value); },
removeItem: (key) => { values.delete(key); },
clear: () => values.clear(),
};
}
describe('usage preferences across updates', () => {
it('preserves older column choices and widths, including previous defaults', () => {
const original = Object.getOwnPropertyDescriptor(globalThis, 'localStorage');
const browser = storage({
'cpa-gui.usage-events-visible-cols.v5': JSON.stringify(['time', 'model', 'provider', 'result', 'total', 'cache', 'latency', 'speed', 'cost', 'effort']),
'cpa-gui.usage-events-col-widths.v3': JSON.stringify({ provider: 135, model: 150 }),
});
Object.defineProperty(globalThis, 'localStorage', { configurable: true, value: browser });
try {
expect(getInitialVisibleColumns()).toEqual(['time', 'model', 'provider', 'result', 'total', 'cache', 'latency', 'speed', 'cost']);
expect(getInitialColumnWidths().provider).toBe(135);
browser.setItem('cpa-gui.usage-events-visible-cols.v5', JSON.stringify(['time', 'provider', 'key', 'source', 'model', 'effort', 'result', 'request', 'latency', 'speed', 'total', 'cache', 'cost']));
expect(getInitialVisibleColumns()).toEqual(['time', 'provider', 'key', 'source', 'model', 'result', 'request', 'latency', 'speed', 'total', 'cache', 'cost']);
browser.removeItem('cpa-gui.usage-events-visible-cols.v5');
browser.setItem('cpa-gui.usage-events-visible-cols.v4', '["provider","model"]');
expect(getInitialVisibleColumns()).toEqual(['provider', 'model']);
browser.setItem('cpa-gui.usage-events-visible-cols.v3', '["source","model"]');
expect(getInitialVisibleColumns()).toEqual(['provider', 'model']);
} finally {
if (original) Object.defineProperty(globalThis, 'localStorage', original);
else Reflect.deleteProperty(globalThis, 'localStorage');
}
});
it('migrates browser preferences and restores them with an empty WebView cache', async () => {
let disk: Record<string, string> = {};
const backend = { load: async () => disk, save: async (values: Record<string, string>) => { disk = values; } };
const oldBrowser = storage({ [pageSize]: '100', [columns]: '["time","provider"]', unrelated: 'private' });
const beforeUpdate = new UsagePreferences(() => oldBrowser);
await beforeUpdate.initialize(backend);
expect(disk).toEqual({ [pageSize]: '100', [columns]: '["time","provider"]' });
const afterUpdate = new UsagePreferences(() => storage());
await afterUpdate.initialize(backend);
expect(afterUpdate.getItem(pageSize)).toBe('100');
expect(afterUpdate.getItem(columns)).toBe('["time","provider"]');
});
it('prefers disk over stale browser defaults and serializes rapid changes', async () => {
let disk: Record<string, string> = { [pageSize]: '100' };
const browser = storage({ [pageSize]: '50' });
const preferences = new UsagePreferences(() => browser);
await preferences.initialize({ load: async () => disk, save: async (values) => {
await new Promise((resolve) => setTimeout(resolve, values[pageSize] === '200' ? 10 : 0));
disk = values;
} });
expect(preferences.getItem(pageSize)).toBe('100');
preferences.setItem(pageSize, '200');
preferences.setItem(pageSize, '100');
preferences.setItem(columns, '["provider"]');
await preferences.flush();
expect(disk).toEqual({ [pageSize]: '100', [columns]: '["provider"]' });
});
it('does not overwrite unreadable disk preferences with defaults', async () => {
let saves = 0;
const preferences = new UsagePreferences(() => storage());
await expect(preferences.initialize({ load: async () => { throw new Error('unreadable'); }, save: async () => { saves++; } })).rejects.toThrow();
preferences.setItem(pageSize, '50');
await preferences.flush();
expect(saves).toBe(0);
});
it('retains preferences when localStorage is unavailable', async () => {
let disk: Record<string, string> = { [pageSize]: '100' };
const preferences = new UsagePreferences(() => { throw new Error('storage unavailable'); });
await preferences.initialize({ load: async () => disk, save: async (values) => { disk = values; } });
expect(preferences.getItem(pageSize)).toBe('100');
preferences.setItem(pageSize, '200');
await preferences.flush();
expect(disk[pageSize]).toBe('200');
});
});