147 lines
5.3 KiB
JavaScript
147 lines
5.3 KiB
JavaScript
import { useState } from 'react';
|
|
import { logError } from '@edx/frontend-platform/logging';
|
|
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
import { useExamAccessToken, useFetchExamAccessToken, useIsExam } from '@edx/frontend-lib-special-exams';
|
|
|
|
import { initializeMockApp } from '../../../../../setupTest';
|
|
import useExamAccess from './useExamAccess';
|
|
|
|
jest.mock('@edx/frontend-platform/logging', () => ({
|
|
logError: jest.fn(),
|
|
}));
|
|
jest.mock('@edx/frontend-lib-special-exams', () => ({
|
|
useExamAccessToken: jest.fn(),
|
|
useFetchExamAccessToken: jest.fn(),
|
|
useIsExam: jest.fn(() => false),
|
|
}));
|
|
|
|
const id = 'test-id';
|
|
|
|
// This object allows us to manipulate the value of the accessToken.
|
|
const testAccessToken = { curr: '' };
|
|
|
|
const mockFetchExamAccessToken = jest.fn().mockImplementation(() => Promise.resolve());
|
|
useFetchExamAccessToken.mockReturnValue(mockFetchExamAccessToken);
|
|
|
|
const mockUseIsExam = (initialState = false) => {
|
|
const [isExam, setIsExam] = useState(initialState);
|
|
|
|
// This setTimeout block is intended to replicate the case where a unit is an exam, but
|
|
// the call to fetch exam metadata has not yet completed. That is the value of isExam starts
|
|
// as false and transitions to true once the call resolves.
|
|
if (!initialState) {
|
|
setTimeout(
|
|
() => setIsExam(true),
|
|
500,
|
|
);
|
|
}
|
|
|
|
return isExam;
|
|
};
|
|
|
|
describe('useExamAccess hook', () => {
|
|
beforeAll(async () => {
|
|
// We need to mock AuthService to implicitly use `getAuthenticatedUser` within `AppContext.Provider`.
|
|
await initializeMockApp();
|
|
});
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
jest.useFakeTimers();
|
|
useExamAccessToken.mockReset();
|
|
|
|
useIsExam.mockImplementation(() => mockUseIsExam());
|
|
useExamAccessToken.mockImplementation(() => testAccessToken.curr);
|
|
});
|
|
describe('behavior', () => {
|
|
it('returns accessToken and blockAccess and doesn\'t call token API if not an exam', () => {
|
|
const { result } = renderHook(() => useExamAccess({ id }));
|
|
const { accessToken, blockAccess } = result.current;
|
|
|
|
expect(accessToken).toEqual('');
|
|
expect(blockAccess).toBe(false);
|
|
expect(mockFetchExamAccessToken).not.toHaveBeenCalled();
|
|
});
|
|
it('returns true for blockAccess if an exam but accessToken not yet fetched', async () => {
|
|
useIsExam.mockImplementation(() => mockUseIsExam(true));
|
|
|
|
const { result } = renderHook(() => useExamAccess({ id }));
|
|
const { accessToken, blockAccess } = result.current;
|
|
|
|
expect(accessToken).toEqual('');
|
|
expect(blockAccess).toBe(true);
|
|
expect(mockFetchExamAccessToken).toHaveBeenCalled();
|
|
|
|
await waitFor(() => {
|
|
expect(result.current).toBeTruthy();
|
|
expect(result.current?.isFetching).toBeFalsy();
|
|
});
|
|
});
|
|
it('returns false for blockAccess if an exam and accessToken fetch succeeds', async () => {
|
|
useIsExam.mockImplementation(() => mockUseIsExam(true));
|
|
const { result } = renderHook(() => useExamAccess({ id }));
|
|
|
|
// We wait for the promise to resolve and for updates to state to complete so that blockAccess is updated.
|
|
await waitFor(() => {
|
|
expect(result.current).toBeTruthy();
|
|
expect(result.current?.isFetching).toBeFalsy();
|
|
});
|
|
|
|
const { accessToken, blockAccess } = result.current;
|
|
|
|
expect(accessToken).toEqual(testAccessToken.curr);
|
|
expect(blockAccess).toBe(false);
|
|
expect(mockFetchExamAccessToken).toHaveBeenCalled();
|
|
});
|
|
it('in progress', async () => {
|
|
const { result } = renderHook(() => useExamAccess({ id }));
|
|
|
|
let { accessToken, blockAccess } = result.current;
|
|
|
|
expect(accessToken).toEqual('');
|
|
expect(blockAccess).toBe(false);
|
|
expect(mockFetchExamAccessToken).not.toHaveBeenCalled();
|
|
|
|
testAccessToken.curr = 'test-access-token';
|
|
|
|
// The runAllTimers will update the value of isExam, and the waitForNextUpdate will
|
|
// wait for call to setBlockAccess in the finally clause of useEffect hook.
|
|
await act(async () => {
|
|
jest.runAllTimers();
|
|
await waitFor(() => {
|
|
expect(result.current).toBeTruthy();
|
|
expect(result.current?.isFetching).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
({ accessToken, blockAccess } = result.current);
|
|
|
|
expect(accessToken).toEqual('test-access-token');
|
|
expect(blockAccess).toBe(false);
|
|
expect(mockFetchExamAccessToken).toHaveBeenCalled();
|
|
});
|
|
it('returns false for blockAccess if an exam and accessToken fetch fails', async () => {
|
|
useIsExam.mockImplementation(() => mockUseIsExam(true));
|
|
|
|
const testError = 'test-error';
|
|
mockFetchExamAccessToken.mockImplementationOnce(() => Promise.reject(testError));
|
|
|
|
const { result } = renderHook(() => useExamAccess({ id }));
|
|
|
|
// We wait for the promise to resolve and for updates to state to complete so that blockAccess is updated.
|
|
await waitFor(() => {
|
|
expect(result.current).toBeTruthy();
|
|
expect(result.current?.isFetching).toBeFalsy();
|
|
});
|
|
|
|
const { accessToken, blockAccess } = result.current;
|
|
|
|
expect(accessToken).toEqual(testAccessToken.curr);
|
|
expect(blockAccess).toBe(false);
|
|
expect(mockFetchExamAccessToken).toHaveBeenCalled();
|
|
await waitFor(() => {
|
|
expect(logError).toHaveBeenCalledWith(testError);
|
|
});
|
|
});
|
|
});
|
|
});
|