EntityStore: add tests for reducer, improve types, ensure error gets added to state
This commit is contained in:
parent
b93a299009
commit
9df2bb4a86
3 changed files with 86 additions and 5 deletions
79
app/soapbox/entity-store/__tests__/reducer.test.ts
Normal file
79
app/soapbox/entity-store/__tests__/reducer.test.ts
Normal 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);
|
||||
});
|
|
@ -48,6 +48,7 @@ const setFetching = (
|
|||
entityType: string,
|
||||
listKey: string | undefined,
|
||||
isFetching: boolean,
|
||||
error?: any,
|
||||
) => {
|
||||
return produce(state, draft => {
|
||||
const cache = draft[entityType] ?? createCache();
|
||||
|
@ -55,6 +56,7 @@ const setFetching = (
|
|||
if (typeof listKey === 'string') {
|
||||
const list = cache.lists[listKey] ?? createList();
|
||||
list.state.fetching = isFetching;
|
||||
list.state.error = error;
|
||||
cache.lists[listKey] = list;
|
||||
}
|
||||
|
||||
|
@ -72,7 +74,7 @@ function reducer(state: Readonly<State> = {}, action: EntityAction): State {
|
|||
case ENTITIES_FETCH_REQUEST:
|
||||
return setFetching(state, action.entityType, action.listKey, true);
|
||||
case ENTITIES_FETCH_FAIL:
|
||||
return setFetching(state, action.entityType, action.listKey, false);
|
||||
return setFetching(state, action.entityType, action.listKey, false, action.error);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ interface Entity {
|
|||
}
|
||||
|
||||
/** Store of entities by ID. */
|
||||
interface EntityStore {
|
||||
[id: string]: Entity | undefined
|
||||
interface EntityStore<TEntity extends Entity = Entity> {
|
||||
[id: string]: TEntity | undefined
|
||||
}
|
||||
|
||||
/** List of entity IDs and fetch state. */
|
||||
|
@ -32,9 +32,9 @@ interface EntityListState {
|
|||
}
|
||||
|
||||
/** Cache data pertaining to a paritcular entity type.. */
|
||||
interface EntityCache {
|
||||
interface EntityCache<TEntity extends Entity = Entity> {
|
||||
/** Map of entities of this type. */
|
||||
store: EntityStore
|
||||
store: EntityStore<TEntity>
|
||||
/** Lists of entity IDs for a particular purpose. */
|
||||
lists: {
|
||||
[listKey: string]: EntityList | undefined
|
||||
|
|
Loading…
Reference in a new issue