-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: refactor public API to use hooks instead of references to expor…
…ted store This commit refactors the public API, used by the frontend-app-learning application to interact with the frontend-lib-special-exams state, to export a series of hooks. Originally, the public API imported the frontend-lib-special-exams store directly and operated on it in a series of exported functions. This posed a problem for our need to merge the frontend-app-learning and frontend-lib-special-exams stores, because the special exams store is initialized in this repository and used by public API. In order to eventually be able to remove the creation of the store in this repository, we have to remove references to the store by interfacing with the Redux more directly by using the useDispatch and useSelector hooks. This commit also exports the root reducer from this library. This root reducer will be imported by the frontend-app-learning application and used to configure its store.
- Loading branch information
1 parent
6ec658d
commit 5fc54c0
Showing
4 changed files
with
64 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,28 @@ | ||
import { examRequiresAccessToken, store } from './data'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import { examRequiresAccessToken } from './data'; | ||
|
||
export const useIsExam = () => { | ||
const { exam } = useSelector(state => state.specialExams); | ||
|
||
export function isExam() { | ||
const { exam } = store.getState().specialExams; | ||
return !!exam?.id; | ||
} | ||
}; | ||
|
||
export const useExamAccessToken = () => { | ||
const { exam, examAccessToken } = useSelector(state => state.specialExams); | ||
|
||
export function getExamAccess() { | ||
const { exam, examAccessToken } = store.getState().specialExams; | ||
if (!exam) { | ||
return ''; | ||
} | ||
|
||
return examAccessToken.exam_access_token; | ||
} | ||
}; | ||
|
||
export const useFetchExamAccessToken = () => { | ||
const { exam } = useSelector(state => state.specialExams); | ||
const dispatch = useDispatch(); | ||
|
||
export async function fetchExamAccess() { | ||
const { exam } = store.getState().specialExams; | ||
const { dispatch } = store; | ||
if (!exam) { | ||
return Promise.resolve(); | ||
} | ||
return dispatch(examRequiresAccessToken()); | ||
} | ||
return () => dispatch(examRequiresAccessToken()); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,60 +1,80 @@ | ||
import { Factory } from 'rosie'; | ||
|
||
import { isExam, getExamAccess, fetchExamAccess } from './api'; | ||
import { store } from './data'; | ||
import { useExamAccessToken, useFetchExamAccessToken, useIsExam } from './api'; | ||
import { initializeTestStore, render } from './setupTest'; | ||
|
||
/** | ||
* Hooks must be run in the scope of a component. To run the hook, wrap it in a test component whose sole | ||
* responsibility it is to run the hook and assign it to a return value that is returned by the function. | ||
* @param {*} hook: the hook function to run | ||
* @param {*} store: an initial store, passed to the call to render | ||
* @returns: the return value of the hook | ||
*/ | ||
const getHookReturnValue = (hook, store) => { | ||
let returnVal; | ||
const TestComponent = () => { | ||
returnVal = hook(); | ||
return null; | ||
}; | ||
render(<TestComponent />, { store }); | ||
return returnVal; | ||
}; | ||
|
||
describe('External API integration tests', () => { | ||
describe('Test isExam with exam', () => { | ||
describe('Test useIsExam with exam', () => { | ||
let store; | ||
|
||
beforeAll(() => { | ||
jest.mock('./data'); | ||
const mockExam = Factory.build('exam', { attempt: Factory.build('attempt') }); | ||
const mockToken = Factory.build('examAccessToken'); | ||
const mockState = { specialExams: { exam: mockExam, examAccessToken: mockToken } }; | ||
store.getState = jest.fn().mockReturnValue(mockState); | ||
store = initializeTestStore(mockState); | ||
}); | ||
|
||
afterAll(() => { | ||
jest.clearAllMocks(); | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
it('isExam should return true if exam is set', () => { | ||
expect(isExam()).toBe(true); | ||
it('useIsExam should return true if exam is set', () => { | ||
expect(getHookReturnValue(useIsExam, store)).toBe(true); | ||
}); | ||
|
||
it('getExamAccess should return exam access token if access token', () => { | ||
expect(getExamAccess()).toBeTruthy(); | ||
it('useExamAccessToken should return exam access token if access token', () => { | ||
expect(getHookReturnValue(useExamAccessToken, store)).toBeTruthy(); | ||
}); | ||
|
||
it('fetchExamAccess should dispatch get exam access token', () => { | ||
const dispatchReturn = fetchExamAccess(); | ||
expect(dispatchReturn).toBeInstanceOf(Promise); | ||
it('useFetchExamAccessToken should dispatch get exam access token', () => { | ||
// The useFetchExamAccessToken hook returns a function that calls dispatch, so we must call the returned | ||
// value to invoke dispatch. | ||
expect(getHookReturnValue(useFetchExamAccessToken, store)()).toBeInstanceOf(Promise); | ||
}); | ||
}); | ||
|
||
describe('Test isExam without exam', () => { | ||
describe('Test useIsExam without exam', () => { | ||
let store; | ||
|
||
beforeAll(() => { | ||
jest.mock('./data'); | ||
const mockState = { specialExams: { exam: null, examAccessToken: null } }; | ||
store.getState = jest.fn().mockReturnValue(mockState); | ||
store = initializeTestStore(mockState); | ||
}); | ||
|
||
afterAll(() => { | ||
jest.clearAllMocks(); | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
it('isExam should return false if exam is not set', () => { | ||
expect(isExam()).toBe(false); | ||
it('useIsExam should return false if exam is not set', () => { | ||
expect(getHookReturnValue(useIsExam, store)).toBe(false); | ||
}); | ||
|
||
it('getExamAccess should return empty string if exam access token not set', () => { | ||
expect(getExamAccess()).toBeFalsy(); | ||
it('useExamAccessToken should return empty string if exam access token not set', () => { | ||
expect(getHookReturnValue(useExamAccessToken, store)).toBeFalsy(); | ||
}); | ||
|
||
it('fetchExamAccess should not dispatch get exam access token', () => { | ||
const dispatchReturn = fetchExamAccess(); | ||
expect(dispatchReturn).toBeInstanceOf(Promise); | ||
it('useFetchExamAccessToken should not dispatch get exam access token', () => { | ||
expect(getHookReturnValue(useFetchExamAccessToken, store)).toBeInstanceOf(Promise); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters