import { resolveStorage, getPersistedState, persistState } from './storage'; import { StorageType } from './enums'; import * as crypto from './crypto'; const mockStorage = () => { let store: Record = {}; return { getItem: jest.fn((key: string) => store[key] || null), setItem: jest.fn((key: string, value: string) => { store[key] = value; }), removeItem: jest.fn((key: string) => { delete store[key]; }), clear: jest.fn(() => { store = {}; }) }; }; describe('storage.ts', () => { const name = 'testStore'; const initialState = { foo: 'bar', count: 1 }; const state = { foo: 'baz', count: 2 }; const options = { enableEncryption: true, encryptionKey: 'secret' }; beforeEach(() => { jest.clearAllMocks(); }); it('resolveStorage returns localStorage or sessionStorage', () => { // Use the default jsdom-provided window.localStorage and window.sessionStorage expect(resolveStorage(StorageType.Local)).toBe(window.localStorage); expect(resolveStorage(StorageType.Session)).toBe(window.sessionStorage); }); it('getPersistedState returns initialState if nothing is stored', async () => { const storage = mockStorage(); const result = await getPersistedState(name, initialState, storage as any); expect(result).toEqual(initialState); }); it('getPersistedState returns merged state if plain JSON is stored', async () => { const storage = mockStorage(); storage.getItem.mockReturnValueOnce(JSON.stringify(state)); const result = await getPersistedState(name, initialState, storage as any); expect(result).toEqual(state); }); it('getPersistedState returns decrypted state if encrypted', async () => { const storage = mockStorage(); const encrypted = crypto.encryptAndSign(state, options.encryptionKey); storage.getItem.mockReturnValueOnce(encrypted); const result = await getPersistedState(name, initialState, storage as any, options); expect(result).toEqual(state); }); it('persistState stores plain JSON if encryption is disabled', async () => { const storage = mockStorage(); await persistState(name, state, storage as any); expect(storage.setItem).toHaveBeenCalledWith(`store:${name}`, JSON.stringify(state)); }); it('persistState stores encrypted if encryption is enabled', async () => { const storage = mockStorage(); await persistState(name, state, storage as any, options); const stored = storage.setItem.mock.calls[0][1]; expect(typeof stored).toBe('string'); // Should be decryptable expect(crypto.verifyAndDecrypt(stored, options.encryptionKey)).toEqual(state); }); it('getPersistedState returns initialState if decryption fails', async () => { const storage = mockStorage(); storage.getItem.mockReturnValueOnce('not-a-json'); const result = await getPersistedState(name, initialState, storage as any, options); expect(result).toEqual(initialState); }); it('getPersistedState returns initialState if verifyAndDecrypt fails', async () => { const storage = mockStorage(); storage.getItem.mockReturnValueOnce('encrypted-data'); jest.spyOn(crypto, 'verifyAndDecrypt').mockReturnValueOnce(null as any); const result = await getPersistedState(name, initialState, storage as any, options); expect(result).toEqual(initialState); }); it('persistState handles errors in setItem', async () => { const storage = mockStorage(); storage.setItem.mockImplementation(() => { throw new Error('fail'); }); const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); await persistState(name, state, storage as any); const callArgs = warnSpy.mock.calls[0][0]; expect(callArgs).toContain('Could not persist state'); warnSpy.mockRestore(); }); });