// @rstest-environment jsdom import { expect, test, beforeEach, rstest } from '@rstest/core'; import { rehydrateApiCache, startApiPersistence, } from '../src/store/rtkqPersist'; import { REHYDRATE_API } from '../src/api/rehydrate'; import { upsertInstance, setActiveInstanceId, instanceStorage, } from '../src/config/instances'; import type { RootState } from '../src/store/index'; beforeEach(() => { localStorage.clear(); const inst = upsertInstance('http://test.local'); setActiveInstanceId(inst.id); }); function apiStateWith(queries: Record) { return { api: { queries, mutations: {}, provided: {}, subscriptions: {}, config: {} }, } as unknown as RootState; } test('rehydrateApiCache dispatches nothing when no cache is stored', () => { const dispatched: unknown[] = []; rehydrateApiCache((a) => dispatched.push(a)); expect(dispatched).toHaveLength(0); }); test('rehydrateApiCache replays a stored cache as a rehydrate action', () => { instanceStorage.set( 'rtkq', JSON.stringify({ queries: { 'getLibrary(undefined)': { status: 'fulfilled', data: [1] } }, mutations: {}, }), ); const dispatched: Array<{ type: string; payload: unknown }> = []; rehydrateApiCache((a) => dispatched.push(a as { type: string; payload: unknown }), ); expect(dispatched).toHaveLength(1); expect(dispatched[0].type).toBe(REHYDRATE_API); expect(dispatched[0].payload).toMatchObject({ queries: { 'getLibrary(undefined)': { status: 'fulfilled' } }, }); }); test('rehydrate payload always carries `provided` (regression: RTKQ reads provided.tags)', () => { // A snapshot persisted before `provided` existed must not crash RTKQ's // invalidation slice, which does `Object.entries(provided.tags ?? {})`. instanceStorage.set( 'rtkq', JSON.stringify({ queries: { 'getLibrary(undefined)': { status: 'fulfilled', data: [] } }, mutations: {}, }), ); const dispatched: Array<{ payload: { provided?: unknown } }> = []; rehydrateApiCache((a) => dispatched.push(a as { payload: { provided?: unknown } }), ); expect(dispatched[0].payload.provided).toEqual({ tags: {}, keys: {} }); }); test('startApiPersistence saves only fulfilled queries after throttle', () => { rstest.useFakeTimers(); let state = apiStateWith({}); let listener: (() => void) | null = null; const store = { getState: () => state, subscribe: (l: () => void) => { listener = l; return () => {}; }, }; startApiPersistence(store); state = apiStateWith({ 'getAlbums(undefined)': { status: 'fulfilled', data: ['a'] }, 'getArtists(undefined)': { status: 'pending' }, 'getTracks(undefined)': { status: 'rejected', error: 'boom' }, }); listener!(); // throttled — nothing yet expect(instanceStorage.get('rtkq')).toBeNull(); rstest.advanceTimersByTime(2000); const saved = JSON.parse(instanceStorage.get('rtkq')!); expect(Object.keys(saved.queries)).toEqual(['getAlbums(undefined)']); expect(saved.mutations).toEqual({}); rstest.useRealTimers(); });