feat: integration tests

This commit is contained in:
Ben Warzeski
2021-10-26 12:26:23 -04:00
parent 39047272ca
commit 955fea510e
2 changed files with 256 additions and 0 deletions

249
src/test/app.test.jsx Normal file
View File

@@ -0,0 +1,249 @@
import React from 'react';
import * as redux from 'redux';
import { Provider } from 'react-redux';
import {
act,
render,
waitFor,
within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import thunk from 'redux-thunk';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import fakeData from 'data/services/lms/fakeData';
import api from 'data/services/lms/api';
import reducers from 'data/reducers';
import { gradingStatuses } from 'data/services/lms/constants';
import messages from 'i18n';
import App from 'App';
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedHttpClient: jest.fn(),
getLoginRedirectUrl: jest.fn(),
}));
const configureStore = () => redux.createStore(
reducers,
redux.compose(redux.applyMiddleware(thunk)),
);
let el;
let store;
let state;
/**
* Simple wrapper for updating the top-level state variable, that also returns the new value
* @return {obj} - current redux store state
*/
const getState = () => {
state = store.getState();
return state;
};
/** Fake Data for quick access */
const submissionIds = [
fakeData.ids.submissionId(0),
fakeData.ids.submissionId(1),
fakeData.ids.submissionId(2),
fakeData.ids.submissionId(3),
fakeData.ids.submissionId(4),
];
const submissions = submissionIds.map(id => fakeData.mockSubmission(id));
const responses = submissions.map(({ response }) => response);
const statuses = submissionIds.map(id => fakeData.mockSubmissionStatus(id));
const resolveFns = {};
/**
* Mock the api with jest functions that can be tested against.
*/
const mockApi = () => {
api.initializeApp = jest.fn(() => new Promise(
(resolve) => {
resolveFns.initialize = () => resolve({
oraMetadata: fakeData.oraMetadata,
courseMetadata: fakeData.courseMetadata,
submissions: fakeData.submissions,
});
},
));
api.fetchSubmission = jest.fn((submissionId) => new Promise(
(resolve) => resolve(fakeData.mockSubmission(submissionId)),
));
api.fetchSubmissionStatus = jest.fn((submissionId) => new Promise(
(resolve) => resolve(fakeData.mockSubmissionStatus(submissionId)),
));
api.fetchSubmissionResponse = jest.fn((submissionId) => new Promise(
(resolve) => resolve({ response: fakeData.mockSubmission(submissionId).response }),
));
};
/**
* load and configure the store, render the element, and populate the top-level state object
*/
const renderEl = async () => {
store = configureStore();
el = await render(
<IntlProvider locale="en" messages={messages.en}>
<Provider store={store}>
<App />
</Provider>
</IntlProvider>,
);
getState();
};
/**
* resolve the initalization promise, and update state object
*/
const initialize = async () => {
resolveFns.initialize();
await act(() => el.findByText(fakeData.ids.username(0)));
getState();
};
/**
* Select the first 5 entries in the table and click the "View Selected Responses" button
* Wait for the review page to show and update the top-level state object.
*/
const makeTableSelections = async () => {
const table = el.getByRole('table');
const rows = table.querySelectorAll('tbody tr');
const checkbox = (index) => within(rows.item(index)).getByTitle('Toggle Row Selected');
const clickIndex = (index) => userEvent.click(checkbox(index));
[0, 1, 2, 3, 4].forEach(clickIndex);
userEvent.click(el.container.querySelector('.view-selected-responses-btn'));
await act(() => el.findByText('Show Rubric'));
getState();
};
/**
* Click the "next" button in review modal
*/
const clickNext = async () => {
userEvent.click(el.getByLabelText('Load next submission'));
};
/**
* Click the "next" button in review modal
*/
const clickPrev = async () => {
userEvent.click(el.getByLabelText('Load previous submission'));
};
/**
* Wait for the prev and next values to be populated based on the current selection index
*/
const waitForNeighbors = async (currentIndex) => {
await waitFor(
() => {
const { prev, next } = getState().grading;
expect(prev).toEqual(
(currentIndex > 0) ? { response: responses[currentIndex - 1] } : null,
);
expect(next).toEqual(
(currentIndex < 4) ? { response: responses[currentIndex + 1] } : null,
);
},
);
};
/**
* Wait for neighbors, and then verify prev, current, and next grading fields have the appropriate
* data. Also ensure that the app is "grading" iff the "current" response's lockStatus is inProgress.
*/
const checkLoadedResponses = async (currentIndex) => {
await waitForNeighbors(currentIndex);
const { prev, current, next } = state.grading;
expect({ prev, current, next }).toEqual({
prev: currentIndex > 0 ? ({ response: responses[currentIndex - 1] }) : null,
current: {
submissionId: submissionIds[currentIndex],
response: submissions[currentIndex].response,
...statuses[currentIndex],
},
next: currentIndex < 4 ? ({ response: responses[currentIndex + 1] }) : null,
});
expect(state.app.showReview).toEqual(true);
const shouldBeGrading = statuses[currentIndex].lockStatus === gradingStatuses.inProgress;
expect(state.app.isGrading).toEqual(shouldBeGrading);
};
describe('ESG app integration tests', () => {
beforeAll(() => mockApi());
test('initialState', async () => {
await renderEl();
expect(state.app).toEqual(jest.requireActual('data/reducers/app').initialState);
expect(state.submissions).toEqual(
jest.requireActual('data/reducers/submissions').initialState,
);
expect(state.grading).toEqual(jest.requireActual('data/reducers/grading').initialState);
});
test('initialization', async () => {
await renderEl();
await initialize();
expect(state.app.courseMetadata).toEqual(fakeData.courseMetadata);
expect(state.app.oraMetadata).toEqual(fakeData.oraMetadata);
expect(state.submissions.allSubmissions).toEqual(fakeData.submissions);
});
describe('table selection', () => {
beforeAll(async () => {
await renderEl();
await initialize();
await makeTableSelections();
});
it('loads selected submission ids', () => {
expect(state.grading.selected).toEqual(submissionIds);
});
test('app flags, { showReview: true, isGrading: false, showRubric: false }', () => {
expect(state.app.showReview).toEqual(true);
expect(state.app.isGrading).toEqual(false);
expect(state.app.showRubric).toEqual(false);
});
it('loads current submission', () => {
const submissionId = fakeData.ids.submissionId(0);
expect(state.grading.current).toEqual({
submissionId,
...fakeData.mockSubmission(submissionId),
});
});
it('loads response for next submission', () => {
expect(state.grading.next).toEqual({
response: fakeData.mockSubmission(submissionIds[1]).response,
});
});
});
describe('review navigation', () => {
test('loads full submission for current, and response for neighbors when navigating', async () => {
await renderEl();
await initialize();
await makeTableSelections();
await clickNext();
await checkLoadedResponses(1);
await clickNext();
await checkLoadedResponses(2);
await clickPrev();
await checkLoadedResponses(1);
await clickNext();
await checkLoadedResponses(2);
await clickNext();
await checkLoadedResponses(3);
await clickNext();
await checkLoadedResponses(4);
await clickPrev();
await checkLoadedResponses(3);
await clickPrev();
await checkLoadedResponses(2);
await clickPrev();
await checkLoadedResponses(1);
await clickPrev();
await checkLoadedResponses(0);
});
});
});

7
src/test/utils.js Normal file
View File

@@ -0,0 +1,7 @@
export const mockSuccess = (returnValFn) => (...args) => (
new Promise((resolve) => resolve(returnValFn(...args)))
);
export const mockFailure = (returnValFn) => (...args) => (
new Promise((resolve, reject) => reject(returnValFn(...args)))
);