/** @vitest-environment jsdom */ import { afterEach, beforeEach, describe, expect, it, vi, } from 'vitest'; import { createPersistentStore } from './createPersistentStore.svelte'; describe('createPersistentStore', () => { let mockLocalStorage: Storage; const testKey = 'test-store-key'; beforeEach(() => { // Mock localStorage const storeMap = new Map(); mockLocalStorage = { get length() { return storeMap.size; }, clear() { storeMap.clear(); }, getItem(key: string) { return storeMap.get(key) ?? null; }, setItem(key: string, value: string) { storeMap.set(key, value); }, removeItem(key: string) { storeMap.delete(key); }, key(index: number) { return Array.from(storeMap.keys())[index] ?? null; }, }; vi.stubGlobal('localStorage', mockLocalStorage); }); afterEach(() => { vi.unstubAllGlobals(); }); describe('Initialization', () => { it('should create store with default value when localStorage is empty', () => { const store = createPersistentStore(testKey, 'default'); expect(store.value).toBe('default'); }); it('should create store with value from localStorage', () => { mockLocalStorage.setItem(testKey, JSON.stringify('stored value')); const store = createPersistentStore(testKey, 'default'); expect(store.value).toBe('stored value'); }); it('should parse JSON from localStorage', () => { const storedValue = { name: 'Test', count: 42 }; mockLocalStorage.setItem(testKey, JSON.stringify(storedValue)); const store = createPersistentStore(testKey, { name: 'Default', count: 0 }); expect(store.value).toEqual(storedValue); }); it('should use default value when localStorage has invalid JSON', () => { const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); mockLocalStorage.setItem(testKey, 'invalid json{'); const store = createPersistentStore(testKey, 'default'); expect(store.value).toBe('default'); expect(consoleSpy).toHaveBeenCalled(); consoleSpy.mockRestore(); }); }); describe('Reading Values', () => { it('should return current value via getter', () => { const store = createPersistentStore(testKey, 'default'); expect(store.value).toBe('default'); }); it('should return updated value after setter', () => { const store = createPersistentStore(testKey, 'default'); store.value = 'updated'; expect(store.value).toBe('updated'); }); it('should preserve type information', () => { interface TestObject { name: string; count: number; } const defaultValue: TestObject = { name: 'Test', count: 0 }; const store = createPersistentStore(testKey, defaultValue); expect(store.value.name).toBe('Test'); expect(store.value.count).toBe(0); }); }); describe('Writing Values', () => { it('should update value when set via setter', () => { const store = createPersistentStore(testKey, 'default'); store.value = 'new value'; expect(store.value).toBe('new value'); }); it('should serialize objects to JSON', () => { const store = createPersistentStore(testKey, { name: 'Default', count: 0 }); store.value = { name: 'Updated', count: 42 }; // The value is updated in the store expect(store.value).toEqual({ name: 'Updated', count: 42 }); }); it('should handle arrays', () => { const store = createPersistentStore(testKey, []); store.value = [1, 2, 3]; expect(store.value).toEqual([1, 2, 3]); }); it('should handle booleans', () => { const store = createPersistentStore(testKey, false); store.value = true; expect(store.value).toBe(true); }); it('should handle null values', () => { const store = createPersistentStore(testKey, null); store.value = 'not null'; expect(store.value).toBe('not null'); }); }); describe('Clear Function', () => { it('should reset value to default when clear is called', () => { const store = createPersistentStore(testKey, 'default'); store.value = 'modified'; store.clear(); expect(store.value).toBe('default'); }); it('should work with object defaults', () => { const defaultValue = { name: 'Default', count: 0 }; const store = createPersistentStore(testKey, defaultValue); store.value = { name: 'Modified', count: 42 }; store.clear(); expect(store.value).toEqual(defaultValue); }); it('should work with array defaults', () => { const defaultValue = [1, 2, 3]; const store = createPersistentStore(testKey, defaultValue); store.value = [4, 5, 6]; store.clear(); expect(store.value).toEqual(defaultValue); }); }); describe('Type Support', () => { it('should work with string type', () => { const store = createPersistentStore(testKey, 'default'); store.value = 'test string'; expect(store.value).toBe('test string'); }); it('should work with number type', () => { const store = createPersistentStore(testKey, 0); store.value = 42; expect(store.value).toBe(42); }); it('should work with boolean type', () => { const store = createPersistentStore(testKey, false); store.value = true; expect(store.value).toBe(true); }); it('should work with object type', () => { interface TestObject { name: string; value: number; } const defaultValue: TestObject = { name: 'Test', value: 0 }; const store = createPersistentStore(testKey, defaultValue); store.value = { name: 'Updated', value: 42 }; expect(store.value.name).toBe('Updated'); expect(store.value.value).toBe(42); }); it('should work with array type', () => { const store = createPersistentStore(testKey, []); store.value = ['a', 'b', 'c']; expect(store.value).toEqual(['a', 'b', 'c']); }); it('should work with null type', () => { const store = createPersistentStore(testKey, null); expect(store.value).toBeNull(); store.value = 'not null'; expect(store.value).toBe('not null'); }); }); describe('Edge Cases', () => { it('should handle empty string', () => { const store = createPersistentStore(testKey, 'default'); store.value = ''; expect(store.value).toBe(''); }); it('should handle zero number', () => { const store = createPersistentStore(testKey, 100); store.value = 0; expect(store.value).toBe(0); }); it('should handle false boolean', () => { const store = createPersistentStore(testKey, true); store.value = false; expect(store.value).toBe(false); }); it('should handle empty array', () => { const store = createPersistentStore(testKey, [1, 2, 3]); store.value = []; expect(store.value).toEqual([]); }); it('should handle empty object', () => { const store = createPersistentStore>(testKey, { a: 1 }); store.value = {}; expect(store.value).toEqual({}); }); it('should handle special characters in string', () => { const store = createPersistentStore(testKey, ''); const specialString = 'Hello "world"\nNew line\tTab'; store.value = specialString; expect(store.value).toBe(specialString); }); it('should handle unicode characters', () => { const store = createPersistentStore(testKey, ''); store.value = 'Hello δΈ–η•Œ 🌍'; expect(store.value).toBe('Hello δΈ–η•Œ 🌍'); }); }); describe('Multiple Instances', () => { it('should handle multiple stores with different keys', () => { const store1 = createPersistentStore('key1', 'value1'); const store2 = createPersistentStore('key2', 'value2'); store1.value = 'updated1'; store2.value = 'updated2'; expect(store1.value).toBe('updated1'); expect(store2.value).toBe('updated2'); }); it('should keep stores independent', () => { const store1 = createPersistentStore('key1', 'default1'); const store2 = createPersistentStore('key2', 'default2'); store1.clear(); expect(store1.value).toBe('default1'); expect(store2.value).toBe('default2'); }); }); describe('Complex Scenarios', () => { it('should handle nested objects', () => { interface NestedObject { user: { name: string; settings: { theme: string; notifications: boolean; }; }; } const defaultValue: NestedObject = { user: { name: 'Test', settings: { theme: 'light', notifications: true }, }, }; const store = createPersistentStore(testKey, defaultValue); store.value = { user: { name: 'Updated', settings: { theme: 'dark', notifications: false }, }, }; expect(store.value).toEqual({ user: { name: 'Updated', settings: { theme: 'dark', notifications: false }, }, }); }); it('should handle arrays of objects', () => { interface Item { id: number; name: string; } const store = createPersistentStore(testKey, []); store.value = [ { id: 1, name: 'First' }, { id: 2, name: 'Second' }, { id: 3, name: 'Third' }, ]; expect(store.value).toHaveLength(3); expect(store.value[0].name).toBe('First'); }); }); });