feat: 🎸 useNewsDetails(child, news)

Returns a richer version of a news item
This commit is contained in:
Johan Öbrink 2021-02-10 11:02:26 +01:00
parent 1826b80d4b
commit 5d4f7515cd
5 changed files with 4228 additions and 4141 deletions

View File

@ -19,7 +19,7 @@
"publish-package": "npm publish --access public" "publish-package": "npm publish --access public"
}, },
"dependencies": { "dependencies": {
"@skolplattformen/embedded-api": "^0.20.0", "@skolplattformen/embedded-api": "^0.22.0",
"react-redux": "^7.2.2", "react-redux": "^7.2.2",
"redux": "^4.0.5" "redux": "^4.0.5"
}, },

View File

@ -34,6 +34,7 @@ const hook = <T>(
selector: StoreSelector<T>, selector: StoreSelector<T>,
apiCaller: (api: Api) => ApiCall<T>, apiCaller: (api: Api) => ApiCall<T>,
): EntityHookResult<T> => { ): EntityHookResult<T> => {
const getState = (): EntityStoreRootState => store.getState() as unknown as EntityStoreRootState
const select = (storeState: EntityStoreRootState) => { const select = (storeState: EntityStoreRootState) => {
const stateMap = selector(storeState) || {} const stateMap = selector(storeState) || {}
const state = stateMap[key] || { status: 'pending', data: defaultValue } const state = stateMap[key] || { status: 'pending', data: defaultValue }
@ -42,7 +43,7 @@ const hook = <T>(
const { const {
api, isLoggedIn, reporter, storage, api, isLoggedIn, reporter, storage,
} = useApi() } = useApi()
const initialState = select(store.getState() as EntityStoreRootState) const initialState = select(getState())
const [state, setState] = useState(initialState) const [state, setState] = useState(initialState)
const dispatch = useDispatch() const dispatch = useDispatch()
@ -70,7 +71,7 @@ const hook = <T>(
useEffect(() => { load() }, [isLoggedIn]) useEffect(() => { load() }, [isLoggedIn])
const listener = () => { const listener = () => {
const newState = select(store.getState() as EntityStoreRootState) const newState = select(getState())
if (newState.status !== state.status if (newState.status !== state.status
|| newState.data !== state.data || newState.data !== state.data
|| newState.error !== state.error) { || newState.error !== state.error) {
@ -138,7 +139,6 @@ export const useNewsDetails = (child: Child, news: NewsItem) => hook<NewsItem>(
(api) => () => api.getNewsDetails(child, news), (api) => () => api.getNewsDetails(child, news),
) )
export const useNotifications = (child: Child) => hook<Notification[]>( export const useNotifications = (child: Child) => hook<Notification[]>(
'NOTIFICATIONS', 'NOTIFICATIONS',
`notifications_${child.id}`, `notifications_${child.id}`,

View File

@ -43,22 +43,22 @@ export interface ExtraActionProps<T> {
saveToCache?: (value: string) => Promise<void> saveToCache?: (value: string) => Promise<void>
} }
export type EntityActionType = 'GET_FROM_API' export type EntityActionType = 'GET_FROM_API'
| 'RESULT_FROM_API' | 'RESULT_FROM_API'
| 'API_ERROR' | 'API_ERROR'
| 'GET_FROM_CACHE' | 'GET_FROM_CACHE'
| 'RESULT_FROM_CACHE' | 'RESULT_FROM_CACHE'
| 'STORE_IN_CACHE' | 'STORE_IN_CACHE'
| 'CLEAR' | 'CLEAR'
export type EntityName = 'USER' export type EntityName = 'USER'
| 'CHILDREN' | 'CHILDREN'
| 'CALENDAR' | 'CALENDAR'
| 'CLASSMATES' | 'CLASSMATES'
| 'MENU' | 'MENU'
| 'NEWS' | 'NEWS'
| 'NEWS_DETAILS' | 'NEWS_DETAILS'
| 'NOTIFICATIONS' | 'NOTIFICATIONS'
| 'SCHEDULE' | 'SCHEDULE'
| 'ALL' | 'ALL'
export interface EntityAction<T> extends Action<EntityActionType> { export interface EntityAction<T> extends Action<EntityActionType> {
entity: EntityName entity: EntityName
data?: T data?: T

View File

@ -13,6 +13,7 @@ describe('useNewsDetails(child, newsItem)', () => {
let api let api
let storage let storage
let response let response
let cached
let child let child
let newsItem let newsItem
const wrapper = ({ children }) => ( const wrapper = ({ children }) => (
@ -25,7 +26,8 @@ describe('useNewsDetails(child, newsItem)', () => {
</ApiProvider> </ApiProvider>
) )
beforeEach(() => { beforeEach(() => {
response = { id: '1337', modified: 'now' } cached = { id: '1337', modified: 'yesterday', body: 'rich and old' }
response = { id: '1337', modified: 'now', body: 'rich and new' }
api = init() api = init()
api.getNewsDetails.mockImplementation(() => ( api.getNewsDetails.mockImplementation(() => (
new Promise((res) => { new Promise((res) => {
@ -33,10 +35,10 @@ describe('useNewsDetails(child, newsItem)', () => {
}) })
)) ))
storage = createStorage({ storage = createStorage({
news_details_1337: { id: '1337', modified: 'yesterday' }, news_details_1337: { ...cached },
}, 2) }, 2)
child = { id: 10 } child = { id: 10 }
newsItem = { id: '1337', modified: 'this morning' } newsItem = { id: '1337', modified: 'now', body: 'simple' }
}) })
afterEach(async () => { afterEach(async () => {
await act(async () => { await act(async () => {
@ -86,7 +88,7 @@ describe('useNewsDetails(child, newsItem)', () => {
await waitForNextUpdate() await waitForNextUpdate()
await waitForNextUpdate() await waitForNextUpdate()
expect(result.current.data).toEqual({ id: '1337' }) expect(result.current.data).toEqual(cached)
}) })
}) })
it('updates status to loading', async () => { it('updates status to loading', async () => {
@ -124,7 +126,7 @@ describe('useNewsDetails(child, newsItem)', () => {
await waitForNextUpdate() await waitForNextUpdate()
await pause(20) await pause(20)
expect(storage.cache.news_1).toEqual('{"id":"1337"}') expect(storage.cache.news_details_1337).toEqual(JSON.stringify(response))
}) })
}) })
it('does not store in cache if fake', async () => { it('does not store in cache if fake', async () => {
@ -138,7 +140,7 @@ describe('useNewsDetails(child, newsItem)', () => {
await waitForNextUpdate() await waitForNextUpdate()
await pause(20) await pause(20)
expect(storage.cache.news_2).toEqual('{"id":"1337"}') expect(storage.cache.news_details_1337).toEqual(JSON.stringify(cached))
}) })
}) })
it('retries if api fails', async () => { it('retries if api fails', async () => {
@ -155,7 +157,7 @@ describe('useNewsDetails(child, newsItem)', () => {
expect(result.current.error).toEqual(error) expect(result.current.error).toEqual(error)
expect(result.current.status).toEqual('loading') expect(result.current.status).toEqual('loading')
expect(result.current.data).toEqual({ id: '1337' }) expect(result.current.data).toEqual({ ...cached })
jest.advanceTimersToNextTimer() jest.advanceTimersToNextTimer()
@ -164,7 +166,7 @@ describe('useNewsDetails(child, newsItem)', () => {
await waitForNextUpdate() await waitForNextUpdate()
expect(result.current.status).toEqual('loaded') expect(result.current.status).toEqual('loaded')
expect(result.current.data).toEqual({ id: '1337' }) expect(result.current.data).toEqual({ ...response })
}) })
}) })
it('gives up after 3 retries', async () => { it('gives up after 3 retries', async () => {
@ -183,7 +185,7 @@ describe('useNewsDetails(child, newsItem)', () => {
expect(result.current.error).toEqual(error) expect(result.current.error).toEqual(error)
expect(result.current.status).toEqual('loading') expect(result.current.status).toEqual('loading')
expect(result.current.data).toEqual({ id: '1337' }) expect(result.current.data).toEqual({ ...cached })
jest.advanceTimersToNextTimer() jest.advanceTimersToNextTimer()
@ -193,7 +195,7 @@ describe('useNewsDetails(child, newsItem)', () => {
expect(result.current.error).toEqual(error) expect(result.current.error).toEqual(error)
expect(result.current.status).toEqual('error') expect(result.current.status).toEqual('error')
expect(result.current.data).toEqual({ id: '1337' }) expect(result.current.data).toEqual({ ...cached })
}) })
}) })
it('reports if api fails', async () => { it('reports if api fails', async () => {

8309
yarn.lock

File diff suppressed because it is too large Load Diff