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,
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue