test(createEntityStore): cover createEntityStore helper with unit tests
This commit is contained in:
@@ -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<TestEntity>();
|
||||||
|
|
||||||
|
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<TestEntity>();
|
||||||
|
|
||||||
|
expect(store).toBeInstanceOf(EntityStore);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Selectors', () => {
|
||||||
|
let store: EntityStore<TestEntity>;
|
||||||
|
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<TestEntity>();
|
||||||
|
|
||||||
|
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<TestEntity>();
|
||||||
|
|
||||||
|
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<TestEntity>([{ 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<TestEntity>([{ 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<TestEntity>([{ 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<TestEntity>([{ 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<TestEntity>([{ 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<TestEntity>([
|
||||||
|
{ 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<TestEntity>([
|
||||||
|
{ 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<TestEntity>([{ 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<TestEntity>([{ 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<TestEntity>([
|
||||||
|
{ 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<TestEntity>([
|
||||||
|
{ 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<TestEntity>([{ 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<TestEntity>([{ 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<TestEntity>([]);
|
||||||
|
|
||||||
|
expect(store.all).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle single entity', () => {
|
||||||
|
const store = createEntityStore<TestEntity>([{ 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<ComplexEntity>([entity]);
|
||||||
|
|
||||||
|
expect(store.getById('1')).toEqual(entity);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle numeric string IDs', () => {
|
||||||
|
const store = createEntityStore<TestEntity>([
|
||||||
|
{ 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<TestEntity>([
|
||||||
|
{ 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<TestEntity>([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<User>([
|
||||||
|
{ id: 'u1', name: 'John', email: 'john@example.com' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const productStore = createEntityStore<Product>([
|
||||||
|
{ 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<TestEntity>();
|
||||||
|
|
||||||
|
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<TestEntity>([
|
||||||
|
{ 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user