thunkActions tests

This commit is contained in:
Ben Warzeski
2021-05-14 12:37:06 -04:00
parent 38324a0fc9
commit 9d6cf2e06b
15 changed files with 386 additions and 444 deletions

View File

@@ -122,6 +122,7 @@ const formatGradeOverrideForDisplay = historyArray => historyArray.map(item => (
const simpleSelectors = simpleSelectorFactory(
({ grades }) => grades,
[
'courseId',
'filteredUsersCount',
'totalUsersCount',
'gradeFormat',

View File

@@ -1,4 +1,5 @@
/* eslint-disable import/prefer-default-export */
import { StrictDict } from 'utils';
import actions from '../actions';
import LmsApiService from '../services/LmsApiService';
@@ -6,7 +7,7 @@ import LmsApiService from '../services/LmsApiService';
const { fetching, gotGradesFrozen } = actions.assignmentTypes;
const { gotBulkManagementConfig } = actions.config;
const fetchAssignmentTypes = courseId => (
export const fetchAssignmentTypes = courseId => (
(dispatch) => {
dispatch(fetching.started());
return LmsApiService.fetchAssignmentTypes(courseId)
@@ -22,4 +23,4 @@ const fetchAssignmentTypes = courseId => (
}
);
export { fetchAssignmentTypes };
export default StrictDict({ fetchAssignmentTypes });

View File

@@ -1,99 +1,55 @@
import axios from 'axios';
import configureMockStore from 'redux-mock-store';
import MockAdapter from 'axios-mock-adapter';
import thunk from 'redux-thunk';
import LmsApiService from '../services/LmsApiService';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { configuration } from '../../config';
import { fetchAssignmentTypes } from './assignmentTypes';
import actions from '../actions';
import * as thunkActions from './assignmentTypes';
import { createTestFetcher } from './testUtils';
const mockStore = configureMockStore([thunk]);
jest.mock('../services/LmsApiService', () => ({
fetchAssignmentTypes: jest.fn(),
}));
jest.mock('@edx/frontend-platform/auth');
const axiosMock = new MockAdapter(axios);
getAuthenticatedHttpClient.mockReturnValue(axios);
axios.isAccessTokenExpired = jest.fn();
axios.isAccessTokenExpired.mockReturnValue(false);
describe('actions', () => {
afterEach(() => {
axiosMock.reset();
});
const responseData = {
assignment_types: {
some: 'types',
other: 'TYpeS',
},
grades_frozen: 'bOOl',
can_see_bulk_management: 'BooL',
};
describe('assignmentType thunkActions', () => {
describe('fetchAssignmentTypes', () => {
const courseId = 'course-v1:edX+DemoX+Demo_Course';
const responseData = {
assignment_types: {
Exam: {
drop_count: 0,
min_count: 1,
short_label: 'Exam',
type: 'Exam',
weight: 0.25,
},
Homework: {
drop_count: 1,
min_count: 3,
short_label: 'Ex',
type: 'Homework',
weight: 0.75,
},
},
grades_frozen: false,
can_see_bulk_management: true,
};
it('dispatches success action after fetching fetchAssignmentTypes', () => {
const expectedActions = [
actions.assignmentTypes.fetching.started(),
actions.assignmentTypes.fetching.received(
Object.keys(responseData.assignment_types),
),
actions.assignmentTypes.gotGradesFrozen(responseData.grades_frozen),
actions.assignmentTypes.config.gotBulkManagementConfig(true),
const testFetch = createTestFetcher(
LmsApiService.fetchAssignmentTypes,
thunkActions.fetchAssignmentTypes,
[courseId],
);
describe('actions dispatched on valid response', () => {
const actionNames = [
'fetching.started',
'fetching.received with data.assignment_types',
'gotGradesFrozen with data.grades_frozen',
'config.gotBulkManagement with data.can_see_bulk_management',
];
const store = mockStore();
axiosMock.onGet(`${configuration.LMS_BASE_URL}/api/grades/v1/gradebook/${courseId}/grading-info?graded_only=true`)
.replyOnce(200, JSON.stringify(responseData));
return store.dispatch(fetchAssignmentTypes(courseId)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
test(actionNames.join(', '), () => testFetch(
(resolve) => resolve({ data: responseData }),
[
actions.assignmentTypes.fetching.started(),
actions.assignmentTypes.fetching.received(Object.keys(responseData.assignment_types)),
actions.assignmentTypes.gotGradesFrozen(responseData.grades_frozen),
actions.config.gotBulkManagementConfig(responseData.can_see_bulk_management),
],
));
});
it('dispatches failure action after fetching cohorts', () => {
const expectedActions = [
actions.assignmentTypes.fetching.started(),
actions.assignmentTypes.fetching.error(),
];
const store = mockStore();
axiosMock.onGet(`${configuration.LMS_BASE_URL}/api/grades/v1/gradebook/${courseId}/grading-info?graded_only=true`)
.replyOnce(500, JSON.stringify({}));
return store.dispatch(fetchAssignmentTypes(courseId)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('dispatches frozen grade action with True value after fetching', () => {
const expectedActions = [
actions.assignmentTypes.fetching.started(),
actions.assignmentTypes.fetching.received(
Object.keys(responseData.assignment_types),
),
actions.assignmentTypes.gotGradesFrozen(true),
actions.assignmentTypes.config.gotBulkManagementConfig(true),
];
const store = mockStore();
responseData.grades_frozen = true;
axiosMock.onGet(`${configuration.LMS_BASE_URL}/api/grades/v1/gradebook/${courseId}/grading-info?graded_only=true`)
.replyOnce(200, JSON.stringify(responseData));
return store.dispatch(fetchAssignmentTypes(courseId)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
describe('actions dispatched on api error', () => {
test('fetching.started, fetching.error', () => testFetch(
(resolve, reject) => reject(),
[
actions.assignmentTypes.fetching.started(),
actions.assignmentTypes.fetching.error(),
],
));
});
});
});

View File

@@ -1,10 +1,10 @@
/* eslint-disable import/prefer-default-export */
import { StrictDict } from 'utils';
import cohorts from '../actions/cohorts';
import LmsApiService from '../services/LmsApiService';
const fetchCohorts = courseId => (
export const fetchCohorts = courseId => (
(dispatch) => {
dispatch(cohorts.fetching.started());
return LmsApiService.fetchCohorts(courseId)
@@ -18,4 +18,4 @@ const fetchCohorts = courseId => (
}
);
export { fetchCohorts };
export default StrictDict({ fetchCohorts });

View File

@@ -1,76 +1,44 @@
import axios from 'axios';
import configureMockStore from 'redux-mock-store';
import MockAdapter from 'axios-mock-adapter';
import thunk from 'redux-thunk';
import LmsApiService from '../services/LmsApiService';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { configuration } from '../../config';
import { fetchCohorts } from './cohorts';
import actions from '../actions';
import * as thunkActions from './cohorts';
import { createTestFetcher } from './testUtils';
const mockStore = configureMockStore([thunk]);
jest.mock('../services/LmsApiService', () => ({
fetchCohorts: jest.fn(),
}));
jest.mock('@edx/frontend-platform/auth');
const axiosMock = new MockAdapter(axios);
getAuthenticatedHttpClient.mockReturnValue(axios);
axios.isAccessTokenExpired = jest.fn();
axios.isAccessTokenExpired.mockReturnValue(false);
describe('cohort thunkActions', () => {
afterEach(() => {
axiosMock.reset();
});
const responseData = {
cohorts: {
some: 'COHorts',
other: 'cohORT$',
},
};
describe('cohorts thunkActions', () => {
describe('fetchCohorts', () => {
const courseId = 'course-v1:edX+DemoX+Demo_Course';
it('dispatches success action after fetching cohorts', () => {
const responseData = {
cohorts: [
{
assignment_type: 'manual',
group_id: null,
id: 1,
name: 'default_group',
user_count: 2,
user_partition_id: null,
},
{
assignment_type: 'auto',
group_id: null,
id: 2,
name: 'auto_group',
user_count: 5,
user_partition_id: null,
}],
};
const expectedActions = [
actions.cohorts.fetching.started(),
actions.cohorts.fetching.received(responseData.cohorts),
];
const store = mockStore();
axiosMock.onGet(`${configuration.LMS_BASE_URL}/courses/${courseId}/cohorts/`)
.replyOnce(200, JSON.stringify(responseData));
return store.dispatch(fetchCohorts(courseId)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
const testFetch = createTestFetcher(
LmsApiService.fetchCohorts,
thunkActions.fetchCohorts,
[courseId],
);
describe('actions dispatched on valid response', () => {
test('fetching.started, fetching.received', () => {
return testFetch((resolve) => resolve({ data: responseData }), [
actions.cohorts.fetching.started(),
actions.cohorts.fetching.received(responseData.cohorts),
]);
});
});
it('dispatches failure action after fetching cohorts', () => {
const expectedActions = [
actions.cohorts.fetching.started(),
actions.cohorts.fetching.error(),
];
const store = mockStore();
axiosMock.onGet(`${configuration.LMS_BASE_URL}/courses/${courseId}/cohorts/`)
.replyOnce(500, JSON.stringify({}));
return store.dispatch(fetchCohorts(courseId)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
describe('actions dispatched on api error', () => {
test('fetching.started, fetching.error', () => testFetch(
(resolve, reject) => reject(),
[
actions.cohorts.fetching.started(),
actions.cohorts.fetching.error(),
],
));
});
});
});

View File

@@ -1,18 +1,18 @@
/* eslint-disable import/prefer-default-export */
import { StrictDict } from 'utils';
import filters from '../actions/filters';
import selectors from '../selectors';
import { fetchGrades } from './grades';
const updateIncludeCourseRoleMembers = (includeCourseRoleMembers) => (dispatch, getState) => {
export const updateIncludeCourseRoleMembers = (includeCourseRoleMembers) => (dispatch, getState) => {
dispatch(filters.update.includeCourseRoleMembers(includeCourseRoleMembers));
const state = getState();
const { cohort, track, assignmentType } = selectors.filters.allFilters(state);
dispatch(fetchGrades(state.grades.courseId, cohort, track, assignmentType));
const courseId = selectors.grades.courseId(state);
dispatch(fetchGrades(courseId, cohort, track, assignmentType));
};
export {
export default StrictDict({
updateIncludeCourseRoleMembers,
};
});

View File

@@ -0,0 +1,47 @@
import selectors from '../selectors';
import actions from '../actions';
import { fetchGrades } from './grades';
import { updateIncludeCourseRoleMembers } from './filters';
jest.mock('./grades', () => ({
fetchGrades: jest.fn((...args) => ({ type: 'fetchGrades', args })),
}));
jest.mock('../selectors', () => ({
__esModule: true,
default: {
grades: { courseId: jest.fn() },
filters: { allFilters: jest.fn() },
},
}));
describe('filters thunkActions', () => {
describe('updateIncludeCourseRoleMembers', () => {
const getState = () => ({});
const testVal = 'Hawaii';
const filters = {
cohort: 'COHort',
track: 'TRacK',
assignmentType: 'Prague',
};
const courseId = 'Some Course ID';
let dispatch;
beforeEach(() => {
dispatch = jest.fn();
selectors.filters.allFilters.mockReturnValue(filters);
selectors.grades.courseId.mockReturnValue(courseId);
updateIncludeCourseRoleMembers(testVal)(dispatch, getState);
});
it('dispatches filters.update.includeCoruseRoleMembers with passed value', () => {
expect(dispatch.mock.calls[0][0]).toEqual(actions.filters.update.includeCourseRoleMembers(testVal));
});
it('dispatches fetchGrades with courseId, cohort, track, and assignmentType', () => {
expect(dispatch.mock.calls[1][0]).toEqual(fetchGrades(
courseId,
filters.cohort,
filters.track,
filters.assignmentType,
));
});
});
});

View File

@@ -1,6 +1,7 @@
/* eslint-disable import/no-self-import */
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { StrictDict } from 'utils';
import grades from '../actions/grades';
import { sortAlphaAsc } from '../actions/utils';
@@ -19,16 +20,16 @@ const {
formatGradeOverrideForDisplay,
} = selectors.grades;
const defaultAssignmentFilter = 'All';
export const defaultAssignmentFilter = 'All';
const fetchBulkUpgradeHistory = courseId => (
export const fetchBulkUpgradeHistory = courseId => (
// todo add loading effect
dispatch => LmsApiService.fetchGradeBulkOperationHistory(courseId).then(
(response) => { dispatch(grades.bulkHistory.received(response)); },
).catch(() => dispatch(grades.bulkHistory.error()))
);
const fetchGrades = (
export const fetchGrades = (
courseId,
cohort,
track,
@@ -88,7 +89,7 @@ const fetchGrades = (
}
);
const fetchGradeOverrideHistory = (subsectionId, userId) => (
export const fetchGradeOverrideHistory = (subsectionId, userId) => (
dispatch => LmsApiService.fetchGradeOverrideHistory(subsectionId, userId)
.then(response => response.data)
.then((data) => {
@@ -116,7 +117,7 @@ const fetchGradeOverrideHistory = (subsectionId, userId) => (
})
);
const fetchMatchingUserGrades = (
export const fetchMatchingUserGrades = (
courseId,
searchText,
cohort,
@@ -129,7 +130,7 @@ const fetchMatchingUserGrades = (
return module.fetchGrades(courseId, cohort, track, assignmentType, newOptions);
};
const fetchPrevNextGrades = (endpoint, courseId, cohort, track, assignmentType) => (
export const fetchPrevNextGrades = (endpoint, courseId, cohort, track, assignmentType) => (
(dispatch) => {
dispatch(grades.fetching.started());
return getAuthenticatedHttpClient().get(endpoint)
@@ -154,7 +155,7 @@ const fetchPrevNextGrades = (endpoint, courseId, cohort, track, assignmentType)
}
);
const submitFileUploadFormData = (courseId, formData) => (
export const submitFileUploadFormData = (courseId, formData) => (
(dispatch) => {
dispatch(grades.csvUpload.started());
return LmsApiService.uploadGradeCsv(courseId, formData).then(() => {
@@ -171,7 +172,7 @@ const submitFileUploadFormData = (courseId, formData) => (
}
);
const updateGrades = (courseId, updateData, searchText, cohort, track) => (
export const updateGrades = (courseId, updateData, searchText, cohort, track) => (
(dispatch) => {
dispatch(grades.update.request());
return LmsApiService.updateGradebookData(courseId, updateData)
@@ -194,7 +195,7 @@ const updateGrades = (courseId, updateData, searchText, cohort, track) => (
}
);
const updateGradesIfAssignmentGradeFiltersSet = (
export const updateGradesIfAssignmentGradeFiltersSet = (
courseId,
cohort,
track,
@@ -213,8 +214,7 @@ const updateGradesIfAssignmentGradeFiltersSet = (
}
};
export {
defaultAssignmentFilter,
export default StrictDict({
fetchBulkUpgradeHistory,
fetchGrades,
fetchGradeOverrideHistory,
@@ -223,4 +223,4 @@ export {
submitFileUploadFormData,
updateGrades,
updateGradesIfAssignmentGradeFiltersSet,
};
});

View File

@@ -9,6 +9,8 @@ import { sortAlphaAsc } from '../actions/utils';
import LmsApiService from '../services/LmsApiService';
import selectors from '../selectors';
import { createTestFetcher } from './testUtils';
const mockStore = configureMockStore([thunk]);
const courseId = 'course-v1:edX+DemoX+Demo_Course';
@@ -105,29 +107,6 @@ jest.mock('../selectors', () => ({
selectors.filters.allFilters.mockReturnValue(allFilters);
const createTestFetcher = (
mockedMethod,
thunkAction,
args,
onDispatch,
) => (
resolveFn,
expectedActions,
verifyFn,
) => {
const store = mockStore({});
mockedMethod.mockReturnValue(new Promise(resolve => {
resolve(new Promise(resolveFn));
}));
return store.dispatch(thunkAction(...args)).then(() => {
onDispatch();
if (verifyFn) {
verifyFn();
}
expect(store.getActions()).toEqual(expectedActions);
});
};
describe('grades thunkActions', () => {
let oldSelectors;
const mockSelectors = () => {

View File

@@ -0,0 +1,16 @@
import { StrictDict } from 'utils';
import assignmentTypes from './assignmentTypes';
import cohorts from './cohorts';
import filters from './filters';
import grades from './grades';
import roles from './roles';
import tracks from './tracks';
export default StrictDict({
assignmentTypes,
cohorts,
filters,
grades,
roles,
tracks,
});

View File

@@ -1,4 +1,5 @@
/* eslint-disable import/prefer-default-export */
import { StrictDict } from 'utils';
import roles from '../actions/roles';
import selectors from '../selectors';
@@ -11,23 +12,20 @@ import { fetchAssignmentTypes } from './assignmentTypes';
import LmsApiService from '../services/LmsApiService';
const allowedRoles = ['staff', 'instructor', 'support'];
export const allowedRoles = ['staff', 'instructor', 'support'];
const getRoles = courseId => (
export const fetchRoles = courseId => (
(dispatch, getState) => LmsApiService.fetchUserRoles(courseId)
.then(response => response.data)
.then((response) => {
const isAllowedRole = (role) => (
(role.course_id === courseId) && allowedRoles.includes(role.role)
);
const canUserViewGradebook = (response.is_staff || (response.roles.some(isAllowedRole)));
dispatch(roles.received({ canUserViewGradebook, courseId }));
const {
cohort,
track,
assignmentType,
} = selectors.filters.allFilters(getState());
const { cohort, track, assignmentType } = selectors.filters.allFilters(getState());
if (canUserViewGradebook) {
dispatch(fetchGrades(courseId, cohort, track, assignmentType));
dispatch(fetchTracks(courseId));
@@ -39,6 +37,7 @@ const getRoles = courseId => (
dispatch(roles.errorFetching());
}));
export {
getRoles,
};
export default StrictDict({
allowedRoles,
fetchRoles,
});

View File

@@ -1,166 +1,112 @@
import axios from 'axios';
import configureMockStore from 'redux-mock-store';
import MockAdapter from 'axios-mock-adapter';
import thunk from 'redux-thunk';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { createTestFetcher } from './testUtils';
import { configuration } from '../../config';
import { getRoles } from './roles';
import {
GOT_ROLES,
ERROR_FETCHING_ROLES,
} from '../constants/actionTypes/roles';
import { STARTED_FETCHING_GRADES } from '../constants/actionTypes/grades';
import { STARTED_FETCHING_TRACKS } from '../constants/actionTypes/tracks';
import { STARTED_FETCHING_COHORTS } from '../constants/actionTypes/cohorts';
import { STARTED_FETCHING_ASSIGNMENT_TYPES } from '../constants/actionTypes/assignmentTypes';
import LmsApiService from '../services/LmsApiService';
import actions from '../actions';
import selectors from '../selectors';
const mockStore = configureMockStore([thunk]);
import { fetchAssignmentTypes } from './assignmentTypes';
import { fetchCohorts } from './cohorts';
import { fetchGrades } from './grades';
import { fetchTracks } from './tracks';
jest.mock('@edx/frontend-platform/auth');
const axiosMock = new MockAdapter(axios);
getAuthenticatedHttpClient.mockReturnValue(axios);
axios.isAccessTokenExpired = jest.fn();
axios.isAccessTokenExpired.mockReturnValue(false);
import { allowedRoles, fetchRoles } from './roles';
const course1Id = 'course-v1:edX+DemoX+Demo_Course';
const course2Id = 'course-v1:edX+DemoX+Demo_Course_2';
const rolesUrl = `${configuration.LMS_BASE_URL}/api/enrollment/v1/roles/?course_id=${encodeURIComponent(course1Id)}`;
jest.mock('../selectors', () => ({
__esModule: true,
default: {
filters: {
allFilters: jest.fn(),
},
},
}));
jest.mock('../services/LmsApiService', () => ({
fetchUserRoles: jest.fn(),
}));
jest.mock('./assignmentTypes', () => ({
fetchAssignmentTypes: jest.fn((...args) => ({ type: 'fetchAssignmentTypes', args })),
}));
jest.mock('./cohorts', () => ({
fetchCohorts: jest.fn((...args) => ({ type: 'fetchCohorts', args })),
}));
jest.mock('./grades', () => ({
fetchGrades: jest.fn((...args) => ({ type: 'fetchGrades', args })),
}));
jest.mock('./tracks', () => ({
fetchTracks: jest.fn((...args) => ({ type: 'fetchTracks', args })),
}));
function makeRoleListObj(roles, isGlobalStaff) {
return {
roles,
is_staff: isGlobalStaff,
const courseId = 'course-v1:edX+DemoX+Demo_Course';
const allowedRole = { course_id: courseId, role: allowedRoles[0] };
const responseData = {
roles: [
{ course_id: 'fakeCourseId', role: 'fakeROLE' },
{ couse_id: 'anotherId', role: 'STuff' },
],
is_staff: false,
};
describe('roles thunkActions', () => {
const filters = {
cohort: 'COHort',
track: 'traCK',
assignmentType: 23,
};
}
function makeRoleObj(courseId, role) {
return {
course_id: courseId,
role,
};
}
const course1StaffRole = makeRoleObj(course1Id, 'staff');
const course1DummyRole = makeRoleObj(course1Id, 'dummy');
const course2StaffRole = makeRoleObj(course2Id, 'staff');
const course2DummyRole = makeRoleObj(course2Id, 'dummy');
const urlParams = { cohort: null, track: null };
describe('actions', () => {
afterEach(() => {
axiosMock.reset();
beforeAll(() => {
selectors.filters.allFilters.mockReturnValue(filters);
});
describe('getRoles', () => {
it('dispatches got_roles action and subsequent actions after fetching role that allows gradebook', () => {
const expectedActions = [
{ type: GOT_ROLES, canUserViewGradebook: true, courseId: course1Id },
{ type: STARTED_FETCHING_GRADES },
{ type: STARTED_FETCHING_TRACKS },
{ type: STARTED_FETCHING_COHORTS },
{ type: STARTED_FETCHING_ASSIGNMENT_TYPES },
];
const store = mockStore();
axiosMock.onGet(rolesUrl)
.replyOnce(
200,
JSON.stringify(makeRoleListObj([course1StaffRole, course2DummyRole], false)),
);
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
describe('fetchRoles', () => {
const testFetch = createTestFetcher(
LmsApiService.fetchUserRoles,
fetchRoles,
[courseId],
);
describe('valid response', () => {
describe('cannot view gradebook (not is_staff, and no allowed roles)', () => {
it('dispatches received with canUserViewGradeBook=false and the courseId', () => (
testFetch((resolve) => resolve({ data: responseData }), [
actions.roles.received({
canUserViewGradebook: false,
courseId,
}),
])
));
});
describe('canUserViewGradebook (is_staff or some role is allowed)', () => {
const testCanUserViewGradebookOutput = (resolveData) => {
const resolveFn = (resolve) => resolve({ data: resolveData });
const expectedActions = [
'received with canUserViewGradebook=false and the courseId',
'fetchGrades thunkAction with courseId and filters(cohort, track, and assignmentType)',
'fetchTracks thunkAction with courseId',
'fetchCohorts thunkAction with courseId',
'fetchAssignmentTypes thunkAction with courseId',
];
it(`dispatches the appropriate actions: [\n ${expectedActions.join('\n ')}\n]`, () => testFetch(
resolveFn,
[
actions.roles.received({ canUserViewGradebook: true, courseId }),
fetchGrades(courseId, filters.cohort, filters.track, filters.assignmentType),
fetchTracks(courseId),
fetchCohorts(courseId),
fetchAssignmentTypes(courseId),
],
));
};
describe('is_staff', () => testCanUserViewGradebookOutput({
...responseData,
is_staff: true,
}));
describe('has allowed role', () => testCanUserViewGradebookOutput({
...responseData,
roles: [...responseData.roles, allowedRole],
}));
});
});
it('dispatches got_roles action and other actions after fetching irrelevent roles but user is global staff', () => {
const expectedActions = [
{ type: GOT_ROLES, canUserViewGradebook: true, courseId: course1Id },
{ type: STARTED_FETCHING_GRADES },
{ type: STARTED_FETCHING_TRACKS },
{ type: STARTED_FETCHING_COHORTS },
{ type: STARTED_FETCHING_ASSIGNMENT_TYPES },
];
const store = mockStore();
axiosMock.onGet(rolesUrl)
.replyOnce(
200,
JSON.stringify(makeRoleListObj([course1DummyRole, course2DummyRole], true)),
);
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('dispatches got_roles action and no other actions after fetching role that disallows gradebook', () => {
const expectedActions = [
{
type: GOT_ROLES, canUserViewGradebook: false, courseId: course1Id,
},
];
const store = mockStore();
axiosMock.onGet(rolesUrl)
.replyOnce(
200,
JSON.stringify(makeRoleListObj([course1DummyRole, course2StaffRole], false)),
);
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('dispatches got_roles action and no other actions after fetching empty roles', () => {
const expectedActions = [
{ type: GOT_ROLES, canUserViewGradebook: false, courseId: course1Id },
];
const store = mockStore();
axiosMock.onGet(rolesUrl)
.replyOnce(
200,
JSON.stringify(makeRoleListObj([], false)),
);
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('dispatches got_roles action and other actions after fetching empty roles but user is global staff', () => {
const expectedActions = [
{ type: GOT_ROLES, canUserViewGradebook: true, courseId: course1Id },
{ type: STARTED_FETCHING_GRADES },
{ type: STARTED_FETCHING_TRACKS },
{ type: STARTED_FETCHING_COHORTS },
{ type: STARTED_FETCHING_ASSIGNMENT_TYPES },
];
const store = mockStore();
axiosMock.onGet(rolesUrl)
.replyOnce(
200,
JSON.stringify(makeRoleListObj([], true)),
);
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('dispatches error action after getting an error when trying to get roles', () => {
const expectedActions = [
{ type: ERROR_FETCHING_ROLES },
];
const store = mockStore();
axiosMock.onGet(rolesUrl).replyOnce(400);
return store.dispatch(getRoles(course1Id, urlParams)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
describe('actions dispatched on api error', () => {
test('errorFetching', () => testFetch(
(resolve, reject) => reject(),
[actions.roles.errorFetching()],
));
});
});
});

View File

@@ -0,0 +1,32 @@
/* eslint-disable import/no-extraneous-dependencies */
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const mockStore = configureMockStore([thunk]);
export const createTestFetcher = (
mockedMethod,
thunkAction,
args,
onDispatch,
) => (
resolveFn,
expectedActions,
verifyFn,
) => {
const store = mockStore({});
mockedMethod.mockReturnValue(new Promise(resolve => {
resolve(new Promise(resolveFn));
}));
return store.dispatch(thunkAction(...args)).then(() => {
if (onDispatch) { onDispatch(); }
if (verifyFn) { verifyFn(); }
if (expectedActions !== undefined) {
expect(store.getActions()).toEqual(expectedActions);
}
});
};
export default {
createTestFetcher,
};

View File

@@ -1,15 +1,14 @@
/* eslint-disable import/prefer-default-export */
import { StrictDict } from 'utils';
import tracks from '../actions/tracks';
import selectors from '../selectors';
import {
fetchBulkUpgradeHistory,
} from './grades';
import { fetchBulkUpgradeHistory } from './grades';
import LmsApiService from '../services/LmsApiService';
const fetchTracks = courseId => (
export const fetchTracks = courseId => (
(dispatch) => {
dispatch(tracks.fetching.started());
return LmsApiService.fetchTracks(courseId)
@@ -26,6 +25,6 @@ const fetchTracks = courseId => (
}
);
export {
export default StrictDict({
fetchTracks,
};
});

View File

@@ -1,87 +1,85 @@
import axios from 'axios';
import configureMockStore from 'redux-mock-store';
import MockAdapter from 'axios-mock-adapter';
import thunk from 'redux-thunk';
import { createTestFetcher } from './testUtils';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { configuration } from '../../config';
import LmsApiService from '../services/LmsApiService';
import actions from '../actions';
import selectors from '../selectors';
import { fetchBulkUpgradeHistory } from './grades';
import { fetchTracks } from './tracks';
import {
STARTED_FETCHING_TRACKS,
GOT_TRACKS,
ERROR_FETCHING_TRACKS,
} from '../constants/actionTypes/tracks';
const mockStore = configureMockStore([thunk]);
jest.mock('../services/LmsApiService', () => ({
fetchTracks: jest.fn(),
}));
jest.mock('../selectors', () => ({
__esModule: true,
default: {
tracks: { hasMastersTrack: jest.fn(() => false) },
},
}));
jest.mock('./grades', () => ({
fetchBulkUpgradeHistory: jest.fn((...args) => ({ type: 'fetchBulkUpgradeHistory', args })),
}));
jest.mock('@edx/frontend-platform/auth');
const axiosMock = new MockAdapter(axios);
getAuthenticatedHttpClient.mockReturnValue(axios);
axios.isAccessTokenExpired = jest.fn();
axios.isAccessTokenExpired.mockReturnValue(false);
describe('actions', () => {
afterEach(() => {
axiosMock.reset();
});
const courseId = 'course-v1:edX+DemoX+Demo_Course';
const responseData = {
couse_modes: ['some', 'course', 'modes'],
};
describe('tracjs thunkActions', () => {
describe('fetchTracks', () => {
const courseId = 'course-v1:edX+DemoX+Demo_Course';
const trackUrl = `${configuration.LMS_BASE_URL}/api/enrollment/v1/course/${courseId}?include_expired=1`;
it('dispatches success action after fetching tracks', () => {
const responseData = {
course_modes: [
{
slug: 'audit',
name: 'Audit',
min_price: 0,
suggested_prices: '',
currency: 'usd',
expiration_datetime: null,
description: null,
sku: '68EFFFF',
bulk_sku: null,
},
{
slug: 'verified',
name: 'Verified Certificate',
min_price: 100,
suggested_prices: '',
currency: 'usd',
expiration_datetime: '2021-05-04T18:08:12.644361Z',
description: null,
sku: '8CF08E5',
bulk_sku: 'A5B6DBE',
}],
};
const expectedActions = [
{ type: STARTED_FETCHING_TRACKS },
{ type: GOT_TRACKS, tracks: responseData.course_modes },
];
const store = mockStore();
axiosMock.onGet(trackUrl)
.replyOnce(200, JSON.stringify(responseData));
return store.dispatch(fetchTracks(courseId)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
const testFetch = createTestFetcher(
LmsApiService.fetchTracks,
fetchTracks,
[courseId],
);
describe('valid response', () => {
describe('if not hasMastersTrack(data.course_modes)', () => {
describe('dispatched actions', () => {
beforeEach(() => {
selectors.tracks.hasMastersTrack.mockReturnValue(false);
});
const expectedActions = [
'tracks.fetching.started',
'tracks.fetching.received with course_modes'
];
it(`dispatches [${expectedActions.join(', ')}]`, () => testFetch(
(resolve) => resolve({ data: responseData }),
[
actions.tracks.fetching.started(),
actions.tracks.fetching.received(responseData.course_modes),
],
));
});
});
describe('if hasMastersTrack(data.course_modes)', () => {
describe('dispatched actions', () => {
beforeEach(() => {
selectors.tracks.hasMastersTrack.mockReturnValue(true);
});
const expectedActions = [
'fetching.started',
'fetching.received with course_modes',
'fetchBulkUpgradeHistory thunkAction with courseId',
];
test(`[${expectedActions.join(', ')}]`, () => testFetch(
(resolve) => resolve({ data: responseData }),
[
actions.tracks.fetching.started(),
actions.tracks.fetching.received(responseData.course_modes),
fetchBulkUpgradeHistory(courseId),
],
));
});
});
});
it('dispatches failure action after fetching tracks', () => {
const expectedActions = [
{ type: STARTED_FETCHING_TRACKS },
{ type: ERROR_FETCHING_TRACKS },
];
const store = mockStore();
axiosMock.onGet(trackUrl)
.replyOnce(500, JSON.stringify({}));
return store.dispatch(fetchTracks(courseId)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
describe('actions dispatched on api error', () => {
test('errorFetching', () => testFetch(
(resolve, reject) => reject(),
[
actions.tracks.fetching.started(),
actions.tracks.fetching.error(),
],
));
});
});
});