test(createPersistentStore): cover createPersistentStore helper with unit tests
This commit is contained in:
@@ -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<string, string>();
|
||||||
|
|
||||||
|
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<TestObject>(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<number[]>(testKey, []);
|
||||||
|
|
||||||
|
store.value = [1, 2, 3];
|
||||||
|
|
||||||
|
expect(store.value).toEqual([1, 2, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle booleans', () => {
|
||||||
|
const store = createPersistentStore<boolean>(testKey, false);
|
||||||
|
|
||||||
|
store.value = true;
|
||||||
|
|
||||||
|
expect(store.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle null values', () => {
|
||||||
|
const store = createPersistentStore<string | null>(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<number[]>(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<string>(testKey, 'default');
|
||||||
|
|
||||||
|
store.value = 'test string';
|
||||||
|
|
||||||
|
expect(store.value).toBe('test string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with number type', () => {
|
||||||
|
const store = createPersistentStore<number>(testKey, 0);
|
||||||
|
|
||||||
|
store.value = 42;
|
||||||
|
|
||||||
|
expect(store.value).toBe(42);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with boolean type', () => {
|
||||||
|
const store = createPersistentStore<boolean>(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<TestObject>(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<string[]>(testKey, []);
|
||||||
|
|
||||||
|
store.value = ['a', 'b', 'c'];
|
||||||
|
|
||||||
|
expect(store.value).toEqual(['a', 'b', 'c']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with null type', () => {
|
||||||
|
const store = createPersistentStore<string | null>(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<number>(testKey, 100);
|
||||||
|
|
||||||
|
store.value = 0;
|
||||||
|
|
||||||
|
expect(store.value).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle false boolean', () => {
|
||||||
|
const store = createPersistentStore<boolean>(testKey, true);
|
||||||
|
|
||||||
|
store.value = false;
|
||||||
|
|
||||||
|
expect(store.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty array', () => {
|
||||||
|
const store = createPersistentStore<number[]>(testKey, [1, 2, 3]);
|
||||||
|
|
||||||
|
store.value = [];
|
||||||
|
|
||||||
|
expect(store.value).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty object', () => {
|
||||||
|
const store = createPersistentStore<Record<string, unknown>>(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<NestedObject>(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<Item[]>(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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user