EntityStore: add tests for reducer, improve types, ensure error gets added to state

This commit is contained in:
Alex Gleason 2023-03-13 18:34:42 -05:00
parent b93a299009
commit 9df2bb4a86
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
3 changed files with 86 additions and 5 deletions

View file

@ -0,0 +1,79 @@
import { entitiesFetchFail, entitiesFetchRequest, importEntities } from '../actions';
import reducer from '../reducer';
import type { EntityCache } from '../types';
interface TestEntity {
id: string
msg: string
}
test('import entities', () => {
const entities: TestEntity[] = [
{ id: '1', msg: 'yolo' },
{ id: '2', msg: 'benis' },
{ id: '3', msg: 'boop' },
];
const action = importEntities(entities, 'TestEntity');
const result = reducer(undefined, action);
const cache = result.TestEntity as EntityCache<TestEntity>;
expect(cache.store['1']!.msg).toBe('yolo');
expect(Object.values(cache.lists).length).toBe(0);
});
test('import entities into a list', () => {
const entities: TestEntity[] = [
{ id: '1', msg: 'yolo' },
{ id: '2', msg: 'benis' },
{ id: '3', msg: 'boop' },
];
const action = importEntities(entities, 'TestEntity', 'thingies');
const result = reducer(undefined, action);
const cache = result.TestEntity as EntityCache<TestEntity>;
expect(cache.store['2']!.msg).toBe('benis');
expect(cache.lists.thingies?.ids.size).toBe(3);
// Now try adding an additional item.
const entities2: TestEntity[] = [
{ id: '4', msg: 'hehe' },
];
const action2 = importEntities(entities2, 'TestEntity', 'thingies');
const result2 = reducer(result, action2);
const cache2 = result2.TestEntity as EntityCache<TestEntity>;
expect(cache2.store['4']!.msg).toBe('hehe');
expect(cache2.lists.thingies?.ids.size).toBe(4);
// Finally, update an item.
const entities3: TestEntity[] = [
{ id: '2', msg: 'yolofam' },
];
const action3 = importEntities(entities3, 'TestEntity', 'thingies');
const result3 = reducer(result2, action3);
const cache3 = result3.TestEntity as EntityCache<TestEntity>;
expect(cache3.store['2']!.msg).toBe('yolofam');
expect(cache3.lists.thingies?.ids.size).toBe(4); // unchanged
});
test('fetching updates the list state', () => {
const action = entitiesFetchRequest('TestEntity', 'thingies');
const result = reducer(undefined, action);
expect(result.TestEntity!.lists.thingies!.state.fetching).toBe(true);
});
test('failure adds the error to the state', () => {
const error = new Error('whoopsie');
const action = entitiesFetchFail('TestEntity', 'thingies', error);
const result = reducer(undefined, action);
expect(result.TestEntity!.lists.thingies!.state.error).toBe(error);
});

View file

@ -48,6 +48,7 @@ const setFetching = (
entityType: string, entityType: string,
listKey: string | undefined, listKey: string | undefined,
isFetching: boolean, isFetching: boolean,
error?: any,
) => { ) => {
return produce(state, draft => { return produce(state, draft => {
const cache = draft[entityType] ?? createCache(); const cache = draft[entityType] ?? createCache();
@ -55,6 +56,7 @@ const setFetching = (
if (typeof listKey === 'string') { if (typeof listKey === 'string') {
const list = cache.lists[listKey] ?? createList(); const list = cache.lists[listKey] ?? createList();
list.state.fetching = isFetching; list.state.fetching = isFetching;
list.state.error = error;
cache.lists[listKey] = list; cache.lists[listKey] = list;
} }
@ -72,7 +74,7 @@ function reducer(state: Readonly<State> = {}, action: EntityAction): State {
case ENTITIES_FETCH_REQUEST: case ENTITIES_FETCH_REQUEST:
return setFetching(state, action.entityType, action.listKey, true); return setFetching(state, action.entityType, action.listKey, true);
case ENTITIES_FETCH_FAIL: case ENTITIES_FETCH_FAIL:
return setFetching(state, action.entityType, action.listKey, false); return setFetching(state, action.entityType, action.listKey, false, action.error);
default: default:
return state; return state;
} }

View file

@ -5,8 +5,8 @@ interface Entity {
} }
/** Store of entities by ID. */ /** Store of entities by ID. */
interface EntityStore { interface EntityStore<TEntity extends Entity = Entity> {
[id: string]: Entity | undefined [id: string]: TEntity | undefined
} }
/** List of entity IDs and fetch state. */ /** List of entity IDs and fetch state. */
@ -32,9 +32,9 @@ interface EntityListState {
} }
/** Cache data pertaining to a paritcular entity type.. */ /** Cache data pertaining to a paritcular entity type.. */
interface EntityCache { interface EntityCache<TEntity extends Entity = Entity> {
/** Map of entities of this type. */ /** Map of entities of this type. */
store: EntityStore store: EntityStore<TEntity>
/** Lists of entity IDs for a particular purpose. */ /** Lists of entity IDs for a particular purpose. */
lists: { lists: {
[listKey: string]: EntityList | undefined [listKey: string]: EntityList | undefined