From 206e609a2df1877db8079660b2f96c999e2ca293 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Wed, 18 Feb 2026 20:19:26 +0300 Subject: [PATCH] test(createEntityStore): cover createEntityStore helper with unit tests --- .../createEntityStore.test.ts | 420 ++++++++++++++++++ 1 file changed, 420 insertions(+) create mode 100644 src/shared/lib/helpers/createEntityStore/createEntityStore.test.ts diff --git a/src/shared/lib/helpers/createEntityStore/createEntityStore.test.ts b/src/shared/lib/helpers/createEntityStore/createEntityStore.test.ts new file mode 100644 index 0000000..0e5d11f --- /dev/null +++ b/src/shared/lib/helpers/createEntityStore/createEntityStore.test.ts @@ -0,0 +1,420 @@ +import { + beforeEach, + describe, + expect, + it, +} from 'vitest'; +import { + type Entity, + EntityStore, + createEntityStore, +} from './createEntityStore.svelte'; + +interface TestEntity { + id: string; + name: string; + value: number; +} + +describe('createEntityStore', () => { + describe('Construction and Initialization', () => { + it('should create an empty store when no initial entities are provided', () => { + const store = createEntityStore(); + + expect(store.all).toEqual([]); + }); + + it('should create a store with initial entities', () => { + const initialEntities: TestEntity[] = [ + { id: '1', name: 'First', value: 1 }, + { id: '2', name: 'Second', value: 2 }, + ]; + const store = createEntityStore(initialEntities); + + expect(store.all).toHaveLength(2); + expect(store.all).toEqual(initialEntities); + }); + + it('should create EntityStore instance', () => { + const store = createEntityStore(); + + expect(store).toBeInstanceOf(EntityStore); + }); + }); + + describe('Selectors', () => { + let store: EntityStore; + let entities: TestEntity[]; + + beforeEach(() => { + entities = [ + { id: '1', name: 'First', value: 10 }, + { id: '2', name: 'Second', value: 20 }, + { id: '3', name: 'Third', value: 30 }, + ]; + store = createEntityStore(entities); + }); + + it('should return all entities as an array', () => { + const all = store.all; + + expect(all).toEqual(entities); + expect(all).toHaveLength(3); + }); + + it('should get a single entity by ID', () => { + const entity = store.getById('2'); + + expect(entity).toEqual({ id: '2', name: 'Second', value: 20 }); + }); + + it('should return undefined for non-existent ID', () => { + const entity = store.getById('999'); + + expect(entity).toBeUndefined(); + }); + + it('should get multiple entities by IDs', () => { + const entities = store.getByIds(['1', '3']); + + expect(entities).toEqual([ + { id: '1', name: 'First', value: 10 }, + { id: '3', name: 'Third', value: 30 }, + ]); + }); + + it('should filter out undefined results when getting by IDs', () => { + const entities = store.getByIds(['1', '999', '3']); + + expect(entities).toEqual([ + { id: '1', name: 'First', value: 10 }, + { id: '3', name: 'Third', value: 30 }, + ]); + expect(entities).toHaveLength(2); + }); + + it('should return empty array when no IDs match', () => { + const entities = store.getByIds(['999', '888']); + + expect(entities).toEqual([]); + }); + + it('should check if entity exists by ID', () => { + expect(store.has('1')).toBe(true); + expect(store.has('999')).toBe(false); + }); + }); + + describe('CRUD Operations - Create', () => { + it('should add a single entity', () => { + const store = createEntityStore(); + + store.addOne({ id: '1', name: 'First', value: 1 }); + + expect(store.all).toHaveLength(1); + expect(store.getById('1')).toEqual({ id: '1', name: 'First', value: 1 }); + }); + + it('should add multiple entities at once', () => { + const store = createEntityStore(); + + store.addMany([ + { id: '1', name: 'First', value: 1 }, + { id: '2', name: 'Second', value: 2 }, + { id: '3', name: 'Third', value: 3 }, + ]); + + expect(store.all).toHaveLength(3); + }); + + it('should replace entity when adding with existing ID', () => { + const store = createEntityStore([{ id: '1', name: 'Original', value: 1 }]); + + store.addOne({ id: '1', name: 'Updated', value: 2 }); + + expect(store.all).toHaveLength(1); + expect(store.getById('1')).toEqual({ id: '1', name: 'Updated', value: 2 }); + }); + }); + + describe('CRUD Operations - Update', () => { + it('should update an existing entity', () => { + const store = createEntityStore([{ id: '1', name: 'Original', value: 1 }]); + + store.updateOne('1', { name: 'Updated' }); + + expect(store.getById('1')).toEqual({ id: '1', name: 'Updated', value: 1 }); + }); + + it('should update multiple properties at once', () => { + const store = createEntityStore([{ id: '1', name: 'Original', value: 1 }]); + + store.updateOne('1', { name: 'Updated', value: 2 }); + + expect(store.getById('1')).toEqual({ id: '1', name: 'Updated', value: 2 }); + }); + + it('should do nothing when updating non-existent entity', () => { + const store = createEntityStore([{ id: '1', name: 'Original', value: 1 }]); + + store.updateOne('999', { name: 'Updated' }); + + expect(store.all).toHaveLength(1); + expect(store.getById('1')).toEqual({ id: '1', name: 'Original', value: 1 }); + }); + + it('should preserve entity when no changes are provided', () => { + const store = createEntityStore([{ id: '1', name: 'Original', value: 1 }]); + + store.updateOne('1', {}); + + expect(store.getById('1')).toEqual({ id: '1', name: 'Original', value: 1 }); + }); + }); + + describe('CRUD Operations - Delete', () => { + it('should remove a single entity', () => { + const store = createEntityStore([ + { id: '1', name: 'First', value: 1 }, + { id: '2', name: 'Second', value: 2 }, + ]); + + store.removeOne('1'); + + expect(store.all).toHaveLength(1); + expect(store.getById('1')).toBeUndefined(); + expect(store.getById('2')).toEqual({ id: '2', name: 'Second', value: 2 }); + }); + + it('should remove multiple entities', () => { + const store = createEntityStore([ + { id: '1', name: 'First', value: 1 }, + { id: '2', name: 'Second', value: 2 }, + { id: '3', name: 'Third', value: 3 }, + ]); + + store.removeMany(['1', '3']); + + expect(store.all).toHaveLength(1); + expect(store.getById('2')).toEqual({ id: '2', name: 'Second', value: 2 }); + }); + + it('should do nothing when removing non-existent entity', () => { + const store = createEntityStore([{ id: '1', name: 'First', value: 1 }]); + + store.removeOne('999'); + + expect(store.all).toHaveLength(1); + }); + + it('should handle empty array when removing many', () => { + const store = createEntityStore([{ id: '1', name: 'First', value: 1 }]); + + store.removeMany([]); + + expect(store.all).toHaveLength(1); + }); + }); + + describe('Bulk Operations', () => { + it('should set all entities, replacing existing', () => { + const store = createEntityStore([ + { id: '1', name: 'First', value: 1 }, + { id: '2', name: 'Second', value: 2 }, + ]); + + store.setAll([{ id: '3', name: 'Third', value: 3 }]); + + expect(store.all).toHaveLength(1); + expect(store.getById('1')).toBeUndefined(); + expect(store.getById('3')).toEqual({ id: '3', name: 'Third', value: 3 }); + }); + + it('should clear all entities', () => { + const store = createEntityStore([ + { id: '1', name: 'First', value: 1 }, + { id: '2', name: 'Second', value: 2 }, + ]); + + store.clear(); + + expect(store.all).toEqual([]); + expect(store.all).toHaveLength(0); + }); + }); + + describe('Reactivity with SvelteMap', () => { + it('should return reactive arrays', () => { + const store = createEntityStore([{ id: '1', name: 'First', value: 1 }]); + + // The all getter should return a fresh array (or reactive state) + const first = store.all; + const second = store.all; + + // Both should have the same content + expect(first).toEqual(second); + }); + + it('should reflect changes in subsequent calls', () => { + const store = createEntityStore([{ id: '1', name: 'First', value: 1 }]); + + expect(store.all).toHaveLength(1); + + store.addOne({ id: '2', name: 'Second', value: 2 }); + + expect(store.all).toHaveLength(2); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty initial array', () => { + const store = createEntityStore([]); + + expect(store.all).toEqual([]); + }); + + it('should handle single entity', () => { + const store = createEntityStore([{ id: '1', name: 'First', value: 1 }]); + + expect(store.all).toHaveLength(1); + expect(store.getById('1')).toEqual({ id: '1', name: 'First', value: 1 }); + }); + + it('should handle entities with complex objects', () => { + interface ComplexEntity extends Entity { + id: string; + data: { + nested: { + value: string; + }; + }; + tags: string[]; + } + + const entity: ComplexEntity = { + id: '1', + data: { nested: { value: 'test' } }, + tags: ['a', 'b', 'c'], + }; + + const store = createEntityStore([entity]); + + expect(store.getById('1')).toEqual(entity); + }); + + it('should handle numeric string IDs', () => { + const store = createEntityStore([ + { id: '123', name: 'First', value: 1 }, + { id: '456', name: 'Second', value: 2 }, + ]); + + expect(store.getById('123')).toEqual({ id: '123', name: 'First', value: 1 }); + expect(store.getById('456')).toEqual({ id: '456', name: 'Second', value: 2 }); + }); + + it('should handle UUID-like IDs', () => { + const uuid1 = '550e8400-e29b-41d4-a716-446655440000'; + const uuid2 = '550e8400-e29b-41d4-a716-446655440001'; + + const store = createEntityStore([ + { id: uuid1, name: 'First', value: 1 }, + { id: uuid2, name: 'Second', value: 2 }, + ]); + + expect(store.getById(uuid1)).toEqual({ id: uuid1, name: 'First', value: 1 }); + }); + }); + + describe('Type Safety', () => { + it('should enforce Entity type with id property', () => { + // This test verifies type checking at compile time + const validEntity: TestEntity = { id: '1', name: 'Test', value: 1 }; + + const store = createEntityStore([validEntity]); + + expect(store.getById('1')).toEqual(validEntity); + }); + + it('should work with different entity types', () => { + interface User extends Entity { + id: string; + name: string; + email: string; + } + + interface Product extends Entity { + id: string; + title: string; + price: number; + } + + const userStore = createEntityStore([ + { id: 'u1', name: 'John', email: 'john@example.com' }, + ]); + + const productStore = createEntityStore([ + { id: 'p1', title: 'Widget', price: 9.99 }, + ]); + + expect(userStore.getById('u1')?.email).toBe('john@example.com'); + expect(productStore.getById('p1')?.price).toBe(9.99); + }); + }); + + describe('Large Datasets', () => { + it('should handle large number of entities efficiently', () => { + const entities: TestEntity[] = Array.from({ length: 1000 }, (_, i) => ({ + id: `id-${i}`, + name: `Entity ${i}`, + value: i, + })); + + const store = createEntityStore(entities); + + expect(store.all).toHaveLength(1000); + expect(store.getById('id-500')).toEqual({ + id: 'id-500', + name: 'Entity 500', + value: 500, + }); + }); + + it('should efficiently check existence in large dataset', () => { + const entities: TestEntity[] = Array.from({ length: 1000 }, (_, i) => ({ + id: `id-${i}`, + name: `Entity ${i}`, + value: i, + })); + + const store = createEntityStore(entities); + + expect(store.has('id-999')).toBe(true); + expect(store.has('id-1000')).toBe(false); + }); + }); + + describe('Method Chaining', () => { + it('should support chaining add operations', () => { + const store = createEntityStore(); + + store.addOne({ id: '1', name: 'First', value: 1 }); + store.addOne({ id: '2', name: 'Second', value: 2 }); + store.addOne({ id: '3', name: 'Third', value: 3 }); + + expect(store.all).toHaveLength(3); + }); + + it('should support chaining update operations', () => { + const store = createEntityStore([ + { id: '1', name: 'First', value: 1 }, + { id: '2', name: 'Second', value: 2 }, + ]); + + store.updateOne('1', { value: 10 }); + store.updateOne('2', { value: 20 }); + + expect(store.getById('1')?.value).toBe(10); + expect(store.getById('2')?.value).toBe(20); + }); + }); +});