From 1c3908f89eff896e53b874dc7f7af71c1123031f Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 18 Feb 2026 20:19:47 +0300 Subject: [PATCH] test(createPersistentStore): cover createPersistentStore helper with unit tests --- .../createPersistentStore.test.ts | 377 ++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 src/shared/lib/helpers/createPersistentStore/createPersistentStore.test.ts diff --git a/src/shared/lib/helpers/createPersistentStore/createPersistentStore.test.ts b/src/shared/lib/helpers/createPersistentStore/createPersistentStore.test.ts new file mode 100644 index 0000000..9cbdac3 --- /dev/null +++ b/src/shared/lib/helpers/createPersistentStore/createPersistentStore.test.ts @@ -0,0 +1,377 @@ +/** @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'); + }); + }); +});