Merge pull request #26 from muselesscreator/redux-slices

Redux slices
This commit is contained in:
Ben Warzeski
2021-11-05 15:56:20 -04:00
committed by GitHub
72 changed files with 560 additions and 882 deletions

View File

@@ -5,7 +5,7 @@ import { BrowserRouter as Router } from 'react-router-dom';
import Footer from '@edx/frontend-component-footer';
import selectors from 'data/selectors';
import { selectors } from 'data/redux';
import ListView from 'containers/ListView';
import './App.scss';

View File

@@ -6,8 +6,7 @@ import { Form } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { feedbackRequirement } from 'data/services/lms/constants';
import actions from 'data/actions';
import selectors from 'data/selectors';
import { actions, selectors } from 'data/redux';
import messages from './messages';
/**

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import actions from 'data/actions';
import selectors from 'data/selectors';
import { actions, selectors } from 'data/redux';
import {
feedbackRequirement,
gradeStatuses,
@@ -20,24 +19,19 @@ jest.mock('@edx/paragon', () => ({
},
}));
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
app: {
rubric: {
criterionFeedbackConfig: jest.fn((...args) => ({
rubricCriterionFeedbackConfig: args,
})),
},
},
grading: {
selected: {
criterionFeedback: jest.fn((...args) => ({
selectedCriterionFeedback: args,
})),
gradeStatus: jest.fn((...args) => ({ selectedGradeStatus: args })),
},
},
jest.mock('data/redux/app/selectors', () => ({
rubric: {
criterionFeedbackConfig: jest.fn((...args) => ({
rubricCriterionFeedbackConfig: args,
})),
},
}));
jest.mock('data/redux/grading/selectors', () => ({
selected: {
criterionFeedback: jest.fn((...args) => ({
selectedCriterionFeedback: args,
})),
gradeStatus: jest.fn((...args) => ({ selectedGradeStatus: args })),
},
}));

View File

@@ -5,8 +5,7 @@ import { connect } from 'react-redux';
import { Form } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import actions from 'data/actions';
import selectors from 'data/selectors';
import { actions, selectors } from 'data/redux';
import messages from './messages';
/**

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import actions from 'data/actions';
import selectors from 'data/selectors';
import { actions, selectors } from 'data/redux';
import { formatMessage } from 'testUtils';
import {
RadioCriterion,
@@ -17,23 +16,18 @@ jest.mock('@edx/paragon', () => ({
},
}));
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
app: {
rubric: {
criterionConfig: jest.fn((...args) => ({
rubricCriterionConfig: args,
})),
},
},
grading: {
selected: {
criterionGradeData: jest.fn((...args) => ({
selectedCriterionGradeData: args,
})),
},
},
jest.mock('data/redux/app/selectors', () => ({
rubric: {
criterionConfig: jest.fn((...args) => ({
rubricCriterionConfig: args,
})),
},
}));
jest.mock('data/redux/grading/selectors', () => ({
selected: {
criterionGradeData: jest.fn((...args) => ({
selectedCriterionGradeData: args,
})),
},
}));

View File

@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import { Form, FormControlFeedback } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import { selectors } from 'data/redux';
import messages from './messages';
/**

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import selectors from 'data/selectors';
import { selectors } from 'data/redux';
import { ReviewCriterion, mapStateToProps } from './ReviewCriterion';
import messages from './messages';
@@ -12,23 +12,18 @@ jest.mock('@edx/paragon', () => ({
FormControlFeedback: () => 'FormControlFeedback',
}));
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
app: {
rubric: {
criterionConfig: jest.fn((...args) => ({
rubricCriterionConfig: args,
})),
},
},
grading: {
selected: {
criterionGradeData: jest.fn((...args) => ({
selectedCriterionGradeData: args,
})),
},
},
jest.mock('data/redux/app/selectors', () => ({
rubric: {
criterionConfig: jest.fn((...args) => ({
rubricCriterionConfig: args,
})),
},
}));
jest.mock('data/redux/grading/selectors', () => ({
selected: {
criterionGradeData: jest.fn((...args) => ({
selectedCriterionGradeData: args,
})),
},
}));

View File

@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
import { Form } from '@edx/paragon';
import selectors from 'data/selectors';
import { selectors } from 'data/redux';
import { gradeStatuses } from 'data/services/lms/constants';
import InfoPopover from 'components/InfoPopover';

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import selectors from 'data/selectors';
import { selectors } from 'data/redux';
import { gradeStatuses } from 'data/services/lms/constants';
import { CriterionContainer, mapStateToProps } from '.';
@@ -18,21 +18,16 @@ jest.mock('@edx/paragon', () => ({
},
}));
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
app: {
rubric: {
criterionConfig: jest.fn((...args) => ({
rubricCriterionConfig: args,
})),
},
},
grading: {
selected: {
gradeStatus: jest.fn((...args) => ({ selectedGradeStatus: args })),
},
},
jest.mock('data/redux/app/selectors', () => ({
rubric: {
criterionConfig: jest.fn((...args) => ({
rubricCriterionConfig: args,
})),
},
}));
jest.mock('data/redux/grading/selectors', () => ({
selected: {
gradeStatus: jest.fn((...args) => ({ selectedGradeStatus: args })),
},
}));

View File

@@ -6,7 +6,7 @@ import { ArrowBack, Launch } from '@edx/paragon/icons';
import { Hyperlink, Icon } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import { selectors } from 'data/redux';
import { locationId } from 'data/constants/app';
import urls from 'data/services/lms/urls';
import messages from './messages';

View File

@@ -7,7 +7,7 @@ import {
import * as constants from 'data/constants/app';
import urls from 'data/services/lms/urls';
import selectors from 'data/selectors';
import { selectors } from 'data/redux';
import {
ListViewBreadcrumb,
@@ -23,15 +23,10 @@ jest.mock('@edx/paragon/icons', () => ({
Launch: 'icons.Launch',
}));
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
app: {
courseId: (...args) => ({ courseId: args }),
ora: {
name: (...args) => ({ oraName: args }),
},
},
jest.mock('data/redux/app/selectors', () => ({
courseId: (...args) => ({ courseId: args }),
ora: {
name: (...args) => ({ oraName: args }),
},
}));

View File

@@ -12,8 +12,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { gradingStatuses } from 'data/services/lms/constants';
import selectors from 'data/selectors';
import thunkActions from 'data/thunkActions';
import { selectors, thunkActions } from 'data/redux';
import lmsMessages from 'data/services/lms/messages';
import StatusBadge from 'components/StatusBadge';

View File

@@ -7,8 +7,7 @@ import {
TextFilter,
} from '@edx/paragon';
import selectors from 'data/selectors';
import thunkActions from 'data/thunkActions';
import { selectors, thunkActions } from 'data/redux';
import { gradingStatuses as statuses } from 'data/services/lms/constants';
import StatusBadge from 'components/StatusBadge';
@@ -38,13 +37,20 @@ jest.mock('components/StatusBadge', () => 'StatusBadge');
jest.mock('containers/ReviewModal', () => 'ReviewModal');
jest.mock('./ListViewBreadcrumb', () => 'ListViewBreadcrumb');
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
jest.mock('data/redux', () => ({
selectors: {
submissions: {
listData: (...args) => ({ listData: args }),
},
},
thunkActions: {
app: {
initialize: (...args) => ({ initialize: args }),
},
grading: {
loadSelectionForReview: (...args) => ({ loadSelectionForReview: args }),
},
},
}));
let el;

View File

@@ -8,7 +8,7 @@ import createDOMPurify from 'dompurify';
import parse from 'html-react-parser';
import selectors from 'data/selectors';
import { selectors } from 'data/redux';
import { fileUploadResponseOptions } from 'data/services/lms/constants';
import SubmissionFiles from './SubmissionFiles';

View File

@@ -4,8 +4,8 @@ import { shallow } from 'enzyme';
import createDOMPurify from 'dompurify';
import parse from 'html-react-parser';
import selectors from 'data/selectors';
import { fileUploadResponseOptions } from 'data/services/lms/constants';
import { selectors } from 'data/redux';
import { ResponseDisplay, mapStateToProps } from '.';
@@ -17,9 +17,8 @@ jest.mock('@edx/paragon', () => {
};
});
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
jest.mock('data/redux', () => ({
selectors: {
grading: {
selected: {
response: (state) => ({ response: state }),

View File

@@ -6,8 +6,7 @@ import { Button } from '@edx/paragon';
import { Cancel, Highlight } from '@edx/paragon/icons';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import thunkActions from 'data/thunkActions';
import { selectors, thunkActions } from 'data/redux';
import { gradingStatuses as statuses } from 'data/services/lms/constants';
import StopGradingConfirmModal from './StopGradingConfirmModal';

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import selectors from 'data/selectors';
import thunkActions from 'data/thunkActions';
import { selectors, thunkActions } from 'data/redux';
import { gradingStatuses as statuses } from 'data/services/lms/constants';
import {
@@ -18,15 +17,10 @@ jest.mock('@edx/paragon/icons', () => ({
Cancel: 'Cancel',
Highlight: 'Highlight',
}));
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
grading: {
selected: {
gradeStatus: (state) => ({ gradeStatus: state }),
gradingStatus: (state) => ({ gradingStatus: state }),
},
},
jest.mock('data/redux/grading/selectors', () => ({
selected: {
gradeStatus: (state) => ({ gradeStatus: state }),
gradingStatus: (state) => ({ gradingStatus: state }),
},
}));
jest.mock('./OverrideGradeConfirmModal', () => 'OverrideGradeConfirmModal');

View File

@@ -6,8 +6,7 @@ import { Icon, IconButton } from '@edx/paragon';
import { ChevronLeft, ChevronRight } from '@edx/paragon/icons';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import thunkActions from 'data/thunkActions';
import { selectors, thunkActions } from 'data/redux';
import messages from './messages';
/**

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import selectors from 'data/selectors';
import thunkActions from 'data/thunkActions';
import { selectors, thunkActions } from 'data/redux';
import { formatMessage } from 'testUtils';
@@ -20,20 +19,15 @@ jest.mock('@edx/paragon/icons', () => ({
ChevronLeft: 'ChevronLeft',
ChevronRight: 'ChevronRight',
}));
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
grading: {
prev: {
doesExist: (state) => ({ prevDoesExist: state }),
},
next: {
doesExist: (state) => ({ nextDoesExist: state }),
},
activeIndex: (state) => ({ activeIndex: state }),
selectionLength: (state) => ({ selectionlength: state }),
},
jest.mock('data/redux/grading/selectors', () => ({
prev: {
doesExist: (state) => ({ prevDoesExist: state }),
},
next: {
doesExist: (state) => ({ nextDoesExist: state }),
},
activeIndex: (state) => ({ activeIndex: state }),
selectionLength: (state) => ({ selectionlength: state }),
}));
describe('SubmissionNavigation component', () => {

View File

@@ -5,8 +5,7 @@ import { connect } from 'react-redux';
import { ActionRow, Button } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import actions from 'data/actions';
import selectors from 'data/selectors';
import { actions, selectors } from 'data/redux';
import StatusBadge from 'components/StatusBadge';
import StartGradingButton from './components/StartGradingButton';

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import actions from 'data/actions';
import selectors from 'data/selectors';
import { actions, selectors } from 'data/redux';
import { ReviewActions, mapStateToProps, mapDispatchToProps } from '.';
@@ -10,17 +9,14 @@ jest.mock('@edx/paragon', () => ({
ActionRow: () => 'ActionRow',
Button: () => 'Button',
}));
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
app: { showRubric: (state) => ({ showRubric: state }) },
grading: {
selected: {
gradingStatus: (state) => ({ gradingStatus: state }),
score: (state) => ({ score: state }),
username: (state) => ({ username: state }),
},
},
jest.mock('data/redux/app/selectors', () => ({
showRubric: (state) => ({ showRubric: state }),
}));
jest.mock('data/redux/grading/selectors', () => ({
selected: {
gradingStatus: (state) => ({ gradingStatus: state }),
score: (state) => ({ score: state }),
username: (state) => ({ username: state }),
},
}));
jest.mock('components/StatusBadge', () => 'StatusBadge');

View File

@@ -8,8 +8,7 @@ import {
Col,
} from '@edx/paragon';
import selectors from 'data/selectors';
import actions from 'data/actions';
import { selectors, actions } from 'data/redux';
import ResponseDisplay from 'containers/ResponseDisplay';
import Rubric from 'containers/Rubric';

View File

@@ -40,6 +40,7 @@
.popover.overlay-help-popover {
z-index: 4000;
margin-right: map-get($spacers, 1) !important;
.help-popover-option {
margin-bottom: map-get($spacers, 1);
}

View File

@@ -6,8 +6,7 @@ import { Form } from '@edx/paragon';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { feedbackRequirement } from 'data/services/lms/constants';
import actions from 'data/actions';
import selectors from 'data/selectors';
import { actions, selectors } from 'data/redux';
import InfoPopover from 'components/InfoPopover';
import messages from './messages';

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import actions from 'data/actions';
import selectors from 'data/selectors';
import { actions, selectors } from 'data/redux';
import {
feedbackRequirement,
gradeStatuses,
@@ -27,27 +26,22 @@ jest.mock('@edx/paragon', () => {
return { Form };
});
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
app: {
isGrading: jest.fn((...args) => ({ isGragrding: args })),
rubric: {
feedbackConfig: jest.fn((...args) => ({
rubricFeedbackConfig: args,
})),
feedbackPrompt: jest.fn((...args) => ({
rubricFeedbackPrompt: args,
})),
},
},
grading: {
selected: {
overallFeedback: jest.fn((...args) => ({
selectedOverallFeedback: args,
})),
},
},
jest.mock('data/redux/app/selectors', () => ({
isGrading: jest.fn((...args) => ({ isGragrding: args })),
rubric: {
feedbackConfig: jest.fn((...args) => ({
rubricFeedbackConfig: args,
})),
feedbackPrompt: jest.fn((...args) => ({
rubricFeedbackPrompt: args,
})),
},
}));
jest.mock('data/redux/grading/selectors', () => ({
selected: {
overallFeedback: jest.fn((...args) => ({
selectedOverallFeedback: args,
})),
},
}));

View File

@@ -14,7 +14,9 @@ exports[`Rubric Container snapshot is grading 1`] = `
id="ora-grading.Rubric.rubric"
/>
</h3>
<hr />
<hr
className="m-2.5"
/>
<CriterionContainer
isGrading={true}
key="1"
@@ -71,7 +73,9 @@ exports[`Rubric Container snapshot is not grading 1`] = `
id="ora-grading.Rubric.rubric"
/>
</h3>
<hr />
<hr
className="m-2.5"
/>
<CriterionContainer
isGrading={false}
key="1"

View File

@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import { Card, Button } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import { selectors } from 'data/redux';
import CriterionContainer from 'containers/CriterionContainer';
import RubricFeedback from './RubricFeedback';
@@ -20,7 +20,7 @@ export const Rubric = ({ isGrading, criteriaIndices }) => (
<Card className="grading-rubric-card">
<Card.Body className="grading-rubric-body">
<h3><FormattedMessage {...messages.rubric} /></h3>
<hr />
<hr className="m-2.5" />
{criteriaIndices.map((index) => (
<CriterionContainer
isGrading={isGrading}

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import selectors from 'data/selectors';
import { selectors } from 'data/redux';
import { Rubric, mapStateToProps } from '.';
jest.mock('containers/CriterionContainer', () => 'CriterionContainer');
@@ -14,17 +14,12 @@ jest.mock('@edx/paragon', () => {
return { Button, Card };
});
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
app: {
isGrading: jest.fn((...args) => ({ isGragrding: args })),
rubric: {
criteriaIndices: jest.fn((...args) => ({
rubricCriteriaIndices: args,
})),
},
},
jest.mock('data/redux/app/selectors', () => ({
isGrading: jest.fn((...args) => ({ isGragrding: args })),
rubric: {
criteriaIndices: jest.fn((...args) => ({
rubricCriteriaIndices: args,
})),
},
}));

View File

@@ -1,19 +0,0 @@
import { StrictDict } from 'utils';
import { createActionFactory } from './utils';
export const dataKey = 'app';
const createAction = createActionFactory(dataKey);
export const loadCourseMetadata = createAction('loadCourseMetadata');
export const loadOraMetadata = createAction('loadOraMetadata');
export const setGrading = createAction('setGrading');
export const setShowReview = createAction('setShowReview');
export const toggleShowRubric = createAction('toggleShowRubric');
export default StrictDict({
loadCourseMetadata,
loadOraMetadata,
setGrading,
setShowReview,
toggleShowRubric,
});

View File

@@ -1,22 +0,0 @@
import actions, { dataKey } from './app';
import { testAction, testActionTypes } from './testUtils';
describe('actions', () => {
describe('action types', () => {
const actionTypes = [
actions.loadCourseMetadata,
actions.loadOraMetadata,
actions.setGrading,
actions.setShowReview,
actions.toggleShowRubric,
].map(action => action.toString());
testActionTypes(actionTypes, dataKey);
});
describe('app actions provided', () => {
test('loadCourseMetadata action', () => testAction(actions.loadCourseMetadata));
test('loadOraMetadata action', () => testAction(actions.loadOraMetadata));
test('setGrading action', () => testAction(actions.setGrading));
test('setShowReview action', () => testAction(actions.setShowReview));
test('toggleShowRubric action', () => testAction(actions.toggleShowRubric));
});
});

View File

@@ -1,95 +0,0 @@
import { StrictDict } from 'utils';
import { createActionFactory } from './utils';
export const dataKey = 'grading';
const createAction = createActionFactory(dataKey);
/**
* Load the first of the selected submission list for review, and initializes
* the review pane to the first index.
* @param {obj} submission data for the review/grading view
* {
* {obj} response - api response data
* {obj} gradeData - api grade data
* {str} status - api grade status
* }
*/
const loadSubmission = createAction('loadSubmission');
/**
* Pre-load just the static info about the "next" submission in the review queue.
* Load submission and the learner's response.
* @param {obj} submission ({ response })
*/
const preloadNext = createAction('preloadNext');
/**
* Pre-load just the static info about the "previous" submission in the review queue.
* Load submission and the learner's response.
* @param {obj} submission ({ response })
*/
const preloadPrev = createAction('preloadPrev');
/**
* Load the "next" submission in the selected queue as the current selection, load its current
* status and grade data, and update prev/next accordingly.
* @param {obj} { status, gradeData }
*/
const loadNext = createAction('loadNext');
/**
* Load the "prev" submission in the selected queue as the current selection, load its current
* status and grade data, and update prev/next accordingly.
* @param {obj} { status, gradeData }
*/
const loadPrev = createAction('loadPrev');
/**
* Load the selected submissions, storing their static data in an ordered array and setting the starting
* index at the beginning of the list.
* @param {obj[]} selection - ordered array of submission static data for all selected submissions
*/
const updateSelection = createAction('updateSelection');
// TODO: implement/design data workflow
const rubric = StrictDict({
/*
* update the local version of the rubric-level comment
* @param {string} comment
*/
updateComment: createAction('rubric/comment'),
/*
* update the local version of points for the given criterion
* @param {number} index
* @param {number} points
*/
updateCriterionPoints: createAction('rubric/criterionPoints'),
/*
* update the local version of comment for the given criterion
* @param {number} index
* @param {string} comments
*/
updateCriterionComment: createAction('rubric/criterionComment'),
});
export const startGrading = createAction('grading/start');
export const setRubricFeedback = createAction('grading/setRubricFeedback');
export const setCriterionFeedback = createAction('grading/setCriterionFeedback');
export const setCriterionOption = createAction('grading/setCriterionOption');
export const clearGrade = createAction('grading/clear');
export default StrictDict({
loadSubmission,
preloadNext,
preloadPrev,
loadNext,
loadPrev,
updateSelection,
rubric,
startGrading,
setRubricFeedback,
setCriterionFeedback,
setCriterionOption,
clearGrade,
});

View File

@@ -1,36 +0,0 @@
import actions, { dataKey } from './grading';
import { testAction, testActionTypes } from './testUtils';
describe('actions', () => {
describe('action types', () => {
const actionTypes = [
actions.loadSubmission,
actions.preloadNext,
actions.loadNext,
actions.loadPrev,
actions.updateSelection,
actions.rubric.updateComment,
actions.rubric.updateCriterionPoints,
actions.rubric.updateCriterionComment,
actions.startGrading,
actions.setRubricFeedback,
actions.setCriterionFeedback,
actions.clearGrade,
].map(action => action.toString());
testActionTypes(actionTypes, dataKey);
});
describe('grading actions provided', () => {
test('loadSubmission action', () => testAction(actions.loadSubmission));
test('preloadNext action', () => testAction(actions.preloadNext));
test('loadNext action', () => testAction(actions.loadNext));
test('loadPrev action', () => testAction(actions.loadPrev));
test('updateSelection action', () => testAction(actions.updateSelection));
test('rubric updateComment action', () => testAction(actions.rubric.updateComment));
test('rubric updateCritrionPoints action', () => testAction(actions.rubric.updateCriterionPoints));
test('rubric updateCriterionComment action', () => testAction(actions.rubric.updateCriterionComment));
test('startGrading action', () => testAction(actions.startGrading));
test('setRubricFeedback action', () => testAction(actions.setRubricFeedback));
test('setCriterionFeedback action', () => testAction(actions.setCriterionFeedback));
test('clearGrade action', () => testAction(actions.clearGrade));
});
});

View File

@@ -1,13 +0,0 @@
import { StrictDict } from 'utils';
import app from './app';
import grading from './grading';
import requests from './requests';
import submissions from './submissions';
export default StrictDict({
app,
grading,
requests,
submissions,
});

View File

@@ -1,15 +0,0 @@
import { StrictDict } from 'utils';
import { createActionFactory } from './utils';
export const dataKey = 'requests';
const createAction = createActionFactory(dataKey);
export const startRequest = createAction('startRequest');
export const completeRequest = createAction('completeRequest');
export const failRequest = createAction('failRequest');
export default StrictDict({
startRequest,
completeRequest,
failRequest,
});

View File

@@ -1,18 +0,0 @@
import actions, { dataKey } from './requests';
import { testAction, testActionTypes } from './testUtils';
describe('actions', () => {
describe('action types', () => {
const actionTypes = [
actions.startRequest,
actions.completeRequest,
actions.failRequest,
].map(action => action.toString());
testActionTypes(actionTypes, dataKey);
});
describe('actions provided', () => {
test('startRequest action', () => testAction(actions.startRequest));
test('completeRequest action', () => testAction(actions.completeRequest));
test('failRequest action', () => testAction(actions.failRequest));
});
});

View File

@@ -1,15 +0,0 @@
import { StrictDict } from 'utils';
import { createActionFactory } from './utils';
export const dataKey = 'submissions';
const createAction = createActionFactory(dataKey);
/**
* Load the basic list-level submission data, keyed by submission id
* @param {obj} submissionListData
*/
const loadList = createAction('loadList');
export default StrictDict({
loadList,
});

View File

@@ -1,14 +0,0 @@
import actions, { dataKey } from './submissions';
import { testAction, testActionTypes } from './testUtils';
describe('actions', () => {
describe('action types', () => {
const actionTypes = [
actions.loadList,
].map(action => action.toString());
testActionTypes(actionTypes, dataKey);
});
describe('submissionsactions provided', () => {
test('loadList action', () => testAction(actions.loadList));
});
});

View File

@@ -1,48 +0,0 @@
/**
* testActionTypes(actionTypes, dataKey)
* Takes a list of actionTypes and a module dataKey, and verifies that
* * all actionTypes are unique
* * all actionTypes begin with the dataKey
* @param {string[]} actionTypes - list of action types
* @param {string} dataKey - module data key
*/
export const testActionTypes = (actionTypes, dataKey) => {
test('all types are unique', () => {
expect(actionTypes.length).toEqual((new Set(actionTypes)).size);
});
test('all types begin with the module dataKey', () => {
actionTypes.forEach(type => {
expect(type.startsWith(dataKey)).toEqual(true);
});
});
};
/**
* testAction(action, args, expectedPayload)
* Multi-purpose action creator test function.
* If args/expectedPayload are passed, verifies that it produces the expected output when called
* with the given args.
* If none are passed, (for action creators with basic definition) it tests against a default
* test payload.
* @param {object} action - action creator object/method
* @param {[object]} args - optional payload argument
* @param {[object]} expectedPayload - optional expected payload.
*/
export const testAction = (action, args, expectedPayload) => {
const type = action.toString();
if (args) {
if (Array.isArray(args)) {
expect(action(...args)).toEqual({ type, payload: expectedPayload });
} else {
expect(action(args)).toEqual({ type, payload: expectedPayload });
}
} else {
const payload = { test: 'PAYload' };
expect(action(payload)).toEqual({ type, payload });
}
};
export default {
testAction,
testActionTypes,
};

View File

@@ -1,10 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
const createActionFactory = (dataKey) => (actionKey, ...args) => (
createAction(`${dataKey}/${actionKey}`, ...args)
);
export {
// eslint-disable-next-line import/prefer-default-export
createActionFactory,
};

View File

@@ -1,19 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
import * as utils from './utils';
jest.mock('@reduxjs/toolkit', () => ({
createAction: (key, ...args) => ({ action: key, args }),
}));
describe('redux action utils', () => {
describe('createActionFactory', () => {
it('returns an action creator with the data key', () => {
const dataKey = 'part-of-the-model';
const actionKey = 'an-action';
const args = ['some', 'args'];
expect(utils.createActionFactory(dataKey)(actionKey, ...args)).toEqual(
createAction(`${dataKey}/${actionKey}`, ...args),
);
});
});
});

View File

@@ -1,43 +0,0 @@
import { createReducer } from '@reduxjs/toolkit';
import actions from 'data/actions';
const initialState = {
oraMetadata: {
prompt: '',
name: '',
type: '',
rubricConfig: null,
fileUploadResponseConfig: null,
},
courseMetadata: {
name: '',
number: '',
org: '',
courseId: '',
},
showReview: false,
showRubric: false,
isGrading: false,
};
// eslint-disable-next-line no-unused-vars
const app = createReducer(initialState, {
[actions.app.loadCourseMetadata]: (state, { payload }) => ({ ...state, courseMetadata: payload }),
[actions.app.loadOraMetadata]: (state, { payload }) => ({ ...state, oraMetadata: payload }),
[actions.app.setShowReview]: (state, { payload }) => ({
...state,
showReview: payload,
isReview: state.isGrading && payload, // stop grading when closing review window
showRubric: state.showRubric && payload, // Hide rubric when closing review window
}),
[actions.app.setGrading]: (state, { payload }) => ({
...state,
isGrading: payload,
showRubric: payload || state.showRubric, // open rubric when starting grading
}),
[actions.app.toggleShowRubric]: (state) => ({ ...state, showRubric: !state.showRubric }),
});
export { initialState };
export default app;

View File

@@ -1,159 +0,0 @@
import { createReducer } from '@reduxjs/toolkit';
import { lockStatuses } from 'data/services/lms/constants';
import actions from 'data/actions';
const initialState = {
selected: [
/**
* {
* submissionId: '',
* username: ''
* teamName: ''
* dateSubmitted: 0,
* gradeStatus: '',
* }
*/
],
gradeData: {
/**
* <submissionId>: {
* overallFeedback: '',
* criteria: [{
* orderNum: 0,
* points: 0,
* comments: '',
* }],
* }
*/
},
activeIndex: null,
current: {
/**
* gradeData: {
* score: {
* pointsEarned: 0,
* pointsPossible: 0,
* }
* overallFeedback: '',
* criteria: [{
* name: '',
* feedback: '',
* selectedOption: '',
* }],
* }
* gradeStatus: '',
* response: {
* text: '',
* files: [{
* download_url: '',
* description: '',
* name: '',
* }],
* },
*/
},
prev: null, // { response }
next: null, // { response }
};
/**
* Updates the given state's gradeData entry for the seleted submission,
* overlaying the passed data on top of the existing data for the that
* submission.
* @return {object} - new state
*/
export const updateGradeData = (state, data) => ({
...state,
gradeData: {
...state.gradeData,
[state.current.submissionId]: {
...state.gradeData[state.current.submissionId],
...data,
},
},
});
/**
* Updates the given state's gradeData entry for the seleted submission,
* overlaying the passed data on top of the existing data for the criterion
* at the given index (orderNum) for the rubric.
* @return {object} - new state
*/
export const updateCriterion = (state, orderNum, data) => {
const entry = state.gradeData[state.current.submissionId];
const criteria = {
...entry.criteria,
[orderNum]: { ...entry.criteria[orderNum], ...data },
};
return updateGradeData(state, { ...entry, criteria });
};
// eslint-disable-next-line no-unused-vars
const app = createReducer(initialState, {
[actions.grading.loadSubmission]: (state, { payload }) => ({
...state,
current: { ...payload },
activeIndex: 0,
}),
[actions.grading.preloadNext]: (state, { payload }) => ({ ...state, next: payload }),
[actions.grading.preloadPrev]: (state, { payload }) => ({ ...state, prev: payload }),
[actions.grading.loadNext]: (state, { payload }) => ({
...state,
prev: { response: state.current.response },
current: { response: state.next.response, ...payload },
activeIndex: state.activeIndex + 1,
gradeData: {
...state.gradeData,
[payload.submissionId]: payload.gradeData,
},
next: null,
}),
[actions.grading.loadPrev]: (state, { payload }) => ({
...state,
next: { response: state.current.response },
current: { response: state.prev.response, ...payload },
gradeData: {
...state.gradeData,
[payload.submissionId]: payload.gradeData,
},
activeIndex: state.activeIndex - 1,
prev: null,
}),
[actions.grading.updateSelection]: (state, { payload }) => ({
...state,
selected: payload,
activeIndex: 0,
}),
[actions.grading.startGrading]: (state, { payload }) => updateGradeData(
{
...state,
current: { ...state.current, lockStatus: lockStatuses.inProgress },
},
{ ...payload },
),
[actions.grading.setRubricFeedback]: (state, { payload }) => (
updateGradeData(state, { overallFeedback: payload })
),
[actions.grading.setCriterionOption]: (state, { payload: { orderNum, value } }) => (
updateCriterion(state, orderNum, { selectedOption: value })
),
[actions.grading.setCriterionFeedback]: (state, { payload: { orderNum, value } }) => (
updateCriterion(state, orderNum, { feedback: value })
),
[actions.grading.clearGrade]: (state) => {
const gradeData = { ...state.gradeData };
delete gradeData[state.current.submissionId];
return {
...state,
gradeData,
current: {
...state.current,
lockStatus: lockStatuses.unlocked,
},
};
},
});
export { initialState };
export default app;

View File

@@ -1,16 +0,0 @@
import { combineReducers } from 'redux';
import app from './app';
import grading from './grading';
import requests from './requests';
import submissions from './submissions';
/* istanbul ignore next */
const rootReducer = combineReducers({
app,
grading,
requests,
submissions,
});
export default rootReducer;

View File

@@ -1,41 +0,0 @@
import { createReducer } from '@reduxjs/toolkit';
import { RequestStates, RequestKeys } from 'data/constants/requests';
import actions from 'data/actions';
const initialState = {
[RequestKeys.initialize]: { status: RequestStates.inactive },
[RequestKeys.fetchSubmission]: { status: RequestStates.inactive },
[RequestKeys.fetchSubmissionStatus]: { status: RequestStates.inactive },
[RequestKeys.setLock]: { status: RequestStates.inactive },
[RequestKeys.prefetchNext]: { status: RequestStates.inactive },
[RequestKeys.prefetchPrev]: { status: RequestStates.inactive },
[RequestKeys.submitGrade]: { status: RequestStates.inactive },
};
// eslint-disable-next-line no-unused-vars
const app = createReducer(initialState, {
[actions.requests.startRequest]: (state, { payload }) => ({
...state,
[payload]: {
status: RequestStates.pending,
},
}),
[actions.requests.completeRequest]: (state, { payload }) => ({
...state,
[payload.requestKey]: {
status: RequestStates.completed,
response: payload.response,
},
}),
[actions.requests.failRequest]: (state, { payload }) => ({
...state,
[payload.requestKey]: {
status: RequestStates.failed,
error: payload.error,
},
}),
});
export { initialState };
export default app;

View File

@@ -1,32 +0,0 @@
import actions from 'data/actions';
const initialState = {
allSubmissions: {
/**
* <submissionId>: {
* submissionId: '',
* username: ''
* teamName: ''
* dateSubmitted: 0,
* gradeStatus: ''
* grade: {
* pointsEarned: 0,
* pointsPossible: 0,
* }
* }
*/
},
};
// eslint-disable-next-line no-unused-vars
const grades = (state = initialState, { type, payload }) => {
switch (type) {
case actions.submissions.loadList.toString():
return { ...state, allSubmissions: payload };
default:
return state;
}
};
export { initialState };
export default grades;

View File

@@ -0,0 +1,2 @@
export { actions, reducer } from './reducer';
export { default as selectors } from './selectors';

View File

@@ -0,0 +1,52 @@
import { StrictDict } from 'utils';
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
oraMetadata: {
prompt: '',
name: '',
type: '',
rubricConfig: null,
},
courseMetadata: {
name: '',
number: '',
org: '',
courseId: '',
},
showReview: false,
showRubric: false,
isGrading: false,
};
// eslint-disable-next-line no-unused-vars
const app = createSlice({
name: 'app',
initialState,
reducers: {
loadCourseMetadata: (state, { payload }) => ({ ...state, courseMetadata: payload }),
loadOraMetadata: (state, { payload }) => ({ ...state, oraMetadata: payload }),
setShowReview: (state, { payload }) => ({
...state,
showReview: payload,
isReview: state.isGrading && payload, // stop grading when closing review window
showRubric: state.showRubric && payload, // Hide rubric when closing review window
}),
setGrading: (state, { payload }) => ({
...state,
isGrading: payload,
showRubric: payload || state.showRubric, // open rubric when starting grading
}),
toggleShowRubric: (state) => ({ ...state, showRubric: !state.showRubric }),
},
});
const actions = StrictDict(app.actions);
const { reducer } = app;
export {
actions,
initialState,
reducer,
};

View File

@@ -4,7 +4,7 @@ import { feedbackRequirement } from 'data/services/lms/constants';
import { StrictDict } from 'utils';
import * as module from './app';
import * as module from './selectors';
export const appSelector = (state) => state.app;
@@ -42,8 +42,8 @@ export const ora = {
*/
type: oraMetadataSelector(data => data.type),
/**
* Return file load response config
* @returns {string} - file load response config
* Returns file load response config
* @return {string} - file upload response config
*/
fileUploadResponseConfig: oraMetadataSelector(data => data.fileUploadResponseConfig),
};

View File

@@ -1,7 +1,7 @@
import { feedbackRequirement } from 'data/services/lms/constants';
// import * in order to mock in-file references
import * as selectors from './app';
import * as selectors from './selectors';
jest.mock('reselect', () => ({
createSelector: jest.fn((preSelectors, cb) => ({ preSelectors, cb })),
@@ -19,6 +19,7 @@ const testState = {
name: 'test-ora-name',
prompt: 'test-ora-prompt',
type: 'test-ora-type',
fileUploadResponseConfig: 'file-upload-response-config',
rubricConfig: {
feedback: 'optional',
criteria: [
@@ -104,12 +105,15 @@ describe('app selectors unit tests', () => {
test('ora.type selector returns type from oraMetadata', () => {
testOraSelector(selectors.ora.type, oraMetadata.type);
});
test('ora.fileUploadResponseConfig selector returns file upload config from oraMetadata', () => {
testOraSelector(selectors.ora.fileUploadResponseConfig, oraMetadata.fileUploadResponseConfig);
});
test('rubricConfig selector returns rubricConfig from oraMetadata', () => {
testOraSelector(selectors.rubric.config, oraMetadata.rubricConfig);
});
test('fileUploadResponseConfig returns fileUploadResponseconfig from oraMetadata', () => {
testOraSelector(
selectors.ora.fileUploadResponseConfig,
oraMetadata.fileUploadResponseConfig,
);
});
});
describe('rubricConfig selectors', () => {
const { rubricConfig } = testState.app.oraMetadata;

View File

@@ -0,0 +1,2 @@
export { actions, reducer } from './reducer';
export { default as selectors } from './selectors';

View File

@@ -0,0 +1,170 @@
import { createSlice } from '@reduxjs/toolkit';
import { StrictDict } from 'utils';
import { lockStatuses } from 'data/services/lms/constants';
const initialState = {
selected: [
/**
* {
* submissionId: '',
* username: ''
* teamName: ''
* dateSubmitted: 0,
* gradeStatus: '',
* }
*/
],
gradeData: {
/**
* <submissionId>: {
* overallFeedback: '',
* criteria: [{
* orderNum: 0,
* points: 0,
* comments: '',
* }],
* }
*/
},
activeIndex: null,
current: {
/**
* gradeData: {
* score: {
* pointsEarned: 0,
* pointsPossible: 0,
* }
* overallFeedback: '',
* criteria: [{
* name: '',
* feedback: '',
* selectedOption: '',
* }],
* }
* gradeStatus: '',
* response: {
* text: '',
* files: [{
* download_url: '',
* description: '',
* name: '',
* }],
* },
*/
},
prev: null, // { response }
next: null, // { response }
};
/**
* Updates the given state's gradeData entry for the seleted submission,
* overlaying the passed data on top of the existing data for the that
* submission.
* @return {object} - new state
*/
export const updateGradeData = (state, data) => ({
...state,
gradeData: {
...state.gradeData,
[state.current.submissionId]: {
...state.gradeData[state.current.submissionId],
...data,
},
},
});
/**
* Updates the given state's gradeData entry for the seleted submission,
* overlaying the passed data on top of the existing data for the criterion
* at the given index (orderNum) for the rubric.
* @return {object} - new state
*/
export const updateCriterion = (state, orderNum, data) => {
const entry = state.gradeData[state.current.submissionId];
const criteria = {
...entry.criteria,
[orderNum]: { ...entry.criteria[orderNum], ...data },
};
return updateGradeData(state, { ...entry, criteria });
};
// eslint-disable-next-line no-unused-vars
const grading = createSlice({
name: 'grading',
initialState,
reducers: {
loadSubmission: (state, { payload }) => ({
...state,
current: { ...payload },
activeIndex: 0,
}),
preloadNext: (state, { payload }) => ({ ...state, next: payload }),
preloadPrev: (state, { payload }) => ({ ...state, prev: payload }),
loadNext: (state, { payload }) => ({
...state,
prev: { response: state.current.response },
current: { response: state.next.response, ...payload },
activeIndex: state.activeIndex + 1,
gradeData: {
...state.gradeData,
[payload.submissionId]: payload.gradeData,
},
next: null,
}),
loadPrev: (state, { payload }) => ({
...state,
next: { response: state.current.response },
current: { response: state.prev.response, ...payload },
gradeData: {
...state.gradeData,
[payload.submissionId]: payload.gradeData,
},
activeIndex: state.activeIndex - 1,
prev: null,
}),
updateSelection: (state, { payload }) => ({
...state,
selected: payload,
activeIndex: 0,
}),
startGrading: (state, { payload }) => updateGradeData(
{
...state,
current: { ...state.current, lockStatus: lockStatuses.inProgress },
},
{ ...payload },
),
setRubricFeedback: (state, { payload }) => (
updateGradeData(state, { overallFeedback: payload })
),
setCriterionOption: (state, { payload: { orderNum, value } }) => (
updateCriterion(state, orderNum, { selectedOption: value })
),
setCriterionFeedback: (state, { payload: { orderNum, value } }) => (
updateCriterion(state, orderNum, { feedback: value })
),
clearGrade: (state) => {
const gradeData = { ...state.gradeData };
delete gradeData[state.current.submissionId];
return {
...state,
gradeData,
current: {
...state.current,
lockStatus: lockStatuses.unlocked,
},
};
},
},
});
const actions = StrictDict(grading.actions);
const { reducer } = grading;
export {
actions,
reducer,
initialState,
};

View File

@@ -2,8 +2,8 @@ import { createSelector } from 'reselect';
import { StrictDict } from 'utils';
import { lockStatuses } from 'data/services/lms/constants';
import submissionsSelectors from './submissions';
import * as module from './grading';
import submissionsSelectors from '../submissions/selectors';
import * as module from './selectors';
export const simpleSelectors = {
selected: state => state.grading.selected,

32
src/data/redux/index.js Normal file
View File

@@ -0,0 +1,32 @@
import { combineReducers } from 'redux';
import { StrictDict } from 'utils';
import * as app from './app';
import * as grading from './grading';
import * as requests from './requests';
import * as submissions from './submissions';
export { default as thunkActions } from './thunkActions';
const modules = {
app,
grading,
requests,
submissions,
};
const moduleProps = (propName) => Object.keys(modules).reduce(
(obj, moduleKey) => ({ ...obj, [moduleKey]: modules[moduleKey][propName] }),
{},
);
const rootReducer = combineReducers(moduleProps('reducer'));
const actions = StrictDict(moduleProps('actions'));
const selectors = StrictDict(moduleProps('selectors'));
export { actions, selectors };
export default rootReducer;

View File

@@ -0,0 +1 @@
export { actions, reducer } from './reducer';

View File

@@ -0,0 +1,52 @@
import { createSlice } from '@reduxjs/toolkit';
import { StrictDict } from 'utils';
import { RequestStates, RequestKeys } from 'data/constants/requests';
const initialState = {
[RequestKeys.initialize]: { status: RequestStates.inactive },
[RequestKeys.fetchSubmission]: { status: RequestStates.inactive },
[RequestKeys.fetchSubmissionStatus]: { status: RequestStates.inactive },
[RequestKeys.setLock]: { status: RequestStates.inactive },
[RequestKeys.prefetchNext]: { status: RequestStates.inactive },
[RequestKeys.prefetchPrev]: { status: RequestStates.inactive },
[RequestKeys.submitGrade]: { status: RequestStates.inactive },
};
// eslint-disable-next-line no-unused-vars
const requests = createSlice({
name: 'requests',
initialState,
reducers: {
startRequest: (state, { payload }) => ({
...state,
[payload]: {
status: RequestStates.pending,
},
}),
completeRequest: (state, { payload }) => ({
...state,
[payload.requestKey]: {
status: RequestStates.completed,
response: payload.response,
},
}),
failRequest: (state, { payload }) => ({
...state,
[payload.requestKey]: {
status: RequestStates.failed,
error: payload.error,
},
}),
},
});
const actions = StrictDict(requests.actions);
const { reducer } = requests;
export {
actions,
reducer,
initialState,
};

View File

@@ -1,6 +1,5 @@
import actions from 'data/actions';
import { RequestStates } from 'data/constants/requests';
import requests, { initialState } from './requests';
import { initialState, actions, reducer } from './reducer';
const testingState = {
...initialState,
@@ -9,39 +8,39 @@ const testingState = {
describe('requests reducer', () => {
it('has initial state', () => {
expect(requests(undefined, {})).toEqual(initialState);
expect(reducer(undefined, {})).toEqual(initialState);
});
const testValue = 'roll for initiative';
const testKey = 'test-key';
describe('handling actions', () => {
describe('requests.startRequest', () => {
describe('startRequest', () => {
it('adds a pending status for the given key', () => {
expect(requests(
expect(reducer(
testingState,
actions.requests.startRequest(testKey),
actions.startRequest(testKey),
)).toEqual({
...testingState,
[testKey]: { status: RequestStates.pending },
});
});
});
describe('requests.completeRequest', () => {
describe('completeRequest', () => {
it('adds a completed status with passed response', () => {
expect(requests(
expect(reducer(
testingState,
actions.requests.completeRequest({ requestKey: testKey, response: testValue }),
actions.completeRequest({ requestKey: testKey, response: testValue }),
)).toEqual({
...testingState,
[testKey]: { status: RequestStates.completed, response: testValue },
});
});
});
describe('requests.failRequest', () => {
describe('failRequest', () => {
it('adds a failed status with passed error', () => {
expect(requests(
expect(reducer(
testingState,
actions.requests.failRequest({ requestKey: testKey, error: testValue }),
actions.failRequest({ requestKey: testKey, error: testValue }),
)).toEqual({
...testingState,
[testKey]: { status: RequestStates.failed, error: testValue },

View File

@@ -0,0 +1,2 @@
export { actions, reducer } from './reducer';
export { default as selectors } from './selectors';

View File

@@ -0,0 +1,39 @@
import { createSlice } from '@reduxjs/toolkit';
import { StrictDict } from 'utils';
const initialState = {
allSubmissions: {
/**
* <submissionId>: {
* submissionId: '',
* username: ''
* teamName: ''
* dateSubmitted: 0,
* gradeStatus: ''
* grade: {
* pointsEarned: 0,
* pointsPossible: 0,
* }
* }
*/
},
};
// eslint-disable-next-line no-unused-vars
const submissions = createSlice({
name: 'submissions',
initialState,
reducers: {
loadList: (state, { payload }) => ({ ...state, allSubmissions: payload }),
},
});
const actions = StrictDict(submissions.actions);
const { reducer } = submissions;
export {
actions,
reducer,
initialState,
};

View File

@@ -1,6 +1,6 @@
import { StrictDict } from 'utils';
import actions from 'data/actions';
import { actions } from 'data/redux';
import { locationId } from 'data/constants/app';
import { initializeApp } from './requests';

View File

@@ -1,6 +1,6 @@
import { locationId } from 'data/constants/app';
import actions from 'data/actions';
import { actions } from 'data/redux';
import thunkActions from './app';
jest.mock('./requests', () => ({

View File

@@ -1,9 +1,9 @@
import { StrictDict } from 'utils';
import { RequestKeys } from 'data/constants/requests';
import actions from 'data/actions';
import selectors from 'data/selectors';
import { gradingStatuses as statuses } from 'data/services/lms/constants';
import { actions, selectors } from 'data/redux';
import * as module from './grading';
import requests from './requests';

View File

@@ -1,7 +1,7 @@
import { RequestKeys } from 'data/constants/requests';
import { gradingStatuses } from 'data/services/lms/constants';
import actions from 'data/actions';
import selectors from 'data/selectors';
import { actions, selectors } from 'data/redux';
import * as thunkActions from './grading';
jest.mock('./requests', () => ({
@@ -12,26 +12,22 @@ jest.mock('./requests', () => ({
submitGrade: (args) => ({ submitGrade: args }),
}));
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
app: {
emptyGrade: (state) => ({ emptyGrade: state }),
},
grading: {
prev: {
submissionId: (state) => ({ prevSubmissionId: state }),
doesExist: jest.fn((state) => ({ prevDoesExist: state })),
},
next: {
submissionId: (state) => ({ prevSubmissionId: state }),
doesExist: jest.fn((state) => ({ nextDoesExist: state })),
},
selected: {
submissionId: (state) => ({ selectedSubmissionId: state }),
gradeData: jest.fn((state) => ({ gradeData: state })),
},
},
jest.mock('data/redux/app/selectors', () => ({
emptyGrade: (state) => ({ emptyGrade: state }),
}));
jest.mock('data/redux/grading/selectors', () => ({
prev: {
submissionId: (state) => ({ prevSubmissionId: state }),
doesExist: jest.fn((state) => ({ prevDoesExist: state })),
},
next: {
submissionId: (state) => ({ prevSubmissionId: state }),
doesExist: jest.fn((state) => ({ nextDoesExist: state })),
},
selected: {
submissionId: (state) => ({ selectedSubmissionId: state }),
gradeData: jest.fn((state) => ({ gradeData: state })),
},
}));

View File

@@ -1,8 +1,9 @@
import { StrictDict } from 'utils';
import { RequestKeys } from 'data/constants/requests';
import actions from 'data/actions';
import { actions } from 'data/redux';
import api from 'data/services/lms/api';
import * as module from './requests';
/**

View File

@@ -1,4 +1,4 @@
import actions from 'data/actions';
import { actions } from 'data/redux';
import { RequestKeys } from 'data/constants/requests';
import api from 'data/services/lms/api';
import * as requests from './requests';

View File

@@ -1,11 +0,0 @@
import { StrictDict } from 'utils';
import app from './app';
import grading from './grading';
import submissions from './submissions';
export default StrictDict({
app,
grading,
submissions,
});

View File

@@ -3,9 +3,7 @@ import thunkMiddleware from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
import { createLogger } from 'redux-logger';
import actions from './actions';
import selectors from './selectors';
import reducers from './reducers';
import reducer, { actions, selectors } from './redux';
export const createStore = () => {
const loggerMiddleware = createLogger();
@@ -13,7 +11,7 @@ export const createStore = () => {
const middleware = [thunkMiddleware, loggerMiddleware];
const store = redux.createStore(
reducers,
reducer,
composeWithDevTools(redux.applyMiddleware(...middleware)),
);

View File

@@ -3,15 +3,16 @@ import thunkMiddleware from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
import { createLogger } from 'redux-logger';
import actions from './actions';
import selectors from './selectors';
import reducers from './reducers';
import rootReducer, { actions, selectors } from 'data/redux';
import exportedStore, { createStore } from './store';
jest.mock('./reducers', () => 'REDUCER');
jest.mock('./actions', () => 'ACTIONS');
jest.mock('./selectors', () => 'SELECTORS');
jest.mock('data/redux', () => ({
__esModule: true,
default: 'REDUCER',
actions: 'ACTIONS',
selectors: 'SELECTORS',
}));
jest.mock('redux-logger', () => ({
createLogger: () => 'logger',
@@ -31,7 +32,7 @@ describe('store aggregator module', () => {
expect(exportedStore).toEqual(createStore());
});
it('creates store with connected reducers', () => {
expect(createStore().reducer).toEqual(reducers);
expect(createStore().reducer).toEqual(rootReducer);
});
describe('middleware', () => {
it('exports thunk and logger middleware, composed and applied with dev tools', () => {

View File

@@ -16,6 +16,7 @@ jest.mock('react-dom', () => ({
render: jest.fn(),
}));
jest.mock('@edx/frontend-platform', () => ({
...jest.requireActual('@edx/frontend-platform'),
APP_READY: 'app-is-ready-key',
initialize: jest.fn(),
subscribe: jest.fn(),

View File

@@ -14,7 +14,7 @@ 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 reducers from 'data/redux';
import { gradingStatuses } from 'data/services/lms/constants';
import messages from 'i18n';
import reviewActionsMessages from 'containers/ReviewActions/messages';
@@ -179,11 +179,15 @@ describe('ESG app integration tests', () => {
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.app).toEqual(
jest.requireActual('data/redux/app/reducer').initialState,
);
expect(state.submissions).toEqual(
jest.requireActual('data/redux/submissions/reducer').initialState,
);
expect(state.grading).toEqual(
jest.requireActual('data/redux/grading/reducer').initialState,
);
expect(state.grading).toEqual(jest.requireActual('data/reducers/grading').initialState);
});
test('initialization', async () => {