diff --git a/src/App.jsx b/src/App.jsx index 3c6105c..710678d 100755 --- a/src/App.jsx +++ b/src/App.jsx @@ -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'; diff --git a/src/containers/CriterionContainer/CriterionFeedback.jsx b/src/containers/CriterionContainer/CriterionFeedback.jsx index 1fa5bfa..b8b7e7e 100644 --- a/src/containers/CriterionContainer/CriterionFeedback.jsx +++ b/src/containers/CriterionContainer/CriterionFeedback.jsx @@ -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'; /** diff --git a/src/containers/CriterionContainer/CriterionFeedback.test.jsx b/src/containers/CriterionContainer/CriterionFeedback.test.jsx index a7e2c3b..aab1239 100644 --- a/src/containers/CriterionContainer/CriterionFeedback.test.jsx +++ b/src/containers/CriterionContainer/CriterionFeedback.test.jsx @@ -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 })), }, })); diff --git a/src/containers/CriterionContainer/RadioCriterion.jsx b/src/containers/CriterionContainer/RadioCriterion.jsx index 53c65aa..746a226 100644 --- a/src/containers/CriterionContainer/RadioCriterion.jsx +++ b/src/containers/CriterionContainer/RadioCriterion.jsx @@ -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'; /** diff --git a/src/containers/CriterionContainer/RadioCriterion.test.jsx b/src/containers/CriterionContainer/RadioCriterion.test.jsx index 116c789..c18ba54 100644 --- a/src/containers/CriterionContainer/RadioCriterion.test.jsx +++ b/src/containers/CriterionContainer/RadioCriterion.test.jsx @@ -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, + })), }, })); diff --git a/src/containers/CriterionContainer/ReviewCriterion.jsx b/src/containers/CriterionContainer/ReviewCriterion.jsx index 3272c4f..bfd9e1f 100644 --- a/src/containers/CriterionContainer/ReviewCriterion.jsx +++ b/src/containers/CriterionContainer/ReviewCriterion.jsx @@ -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'; /** diff --git a/src/containers/CriterionContainer/ReviewCriterion.test.jsx b/src/containers/CriterionContainer/ReviewCriterion.test.jsx index 170b0e1..34a68ac 100644 --- a/src/containers/CriterionContainer/ReviewCriterion.test.jsx +++ b/src/containers/CriterionContainer/ReviewCriterion.test.jsx @@ -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, + })), }, })); diff --git a/src/containers/CriterionContainer/index.jsx b/src/containers/CriterionContainer/index.jsx index dde5582..8fdd69c 100644 --- a/src/containers/CriterionContainer/index.jsx +++ b/src/containers/CriterionContainer/index.jsx @@ -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'; diff --git a/src/containers/CriterionContainer/index.test.jsx b/src/containers/CriterionContainer/index.test.jsx index f7995ce..cb8bef1 100644 --- a/src/containers/CriterionContainer/index.test.jsx +++ b/src/containers/CriterionContainer/index.test.jsx @@ -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 })), }, })); diff --git a/src/containers/ListView/ListViewBreadcrumb.jsx b/src/containers/ListView/ListViewBreadcrumb.jsx index 2e7d30c..25465d4 100644 --- a/src/containers/ListView/ListViewBreadcrumb.jsx +++ b/src/containers/ListView/ListViewBreadcrumb.jsx @@ -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'; diff --git a/src/containers/ListView/ListViewBreadcrumb.test.jsx b/src/containers/ListView/ListViewBreadcrumb.test.jsx index 28da8cb..bca3590 100644 --- a/src/containers/ListView/ListViewBreadcrumb.test.jsx +++ b/src/containers/ListView/ListViewBreadcrumb.test.jsx @@ -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 }), }, })); diff --git a/src/containers/ListView/index.jsx b/src/containers/ListView/index.jsx index b78466e..ee66e7f 100644 --- a/src/containers/ListView/index.jsx +++ b/src/containers/ListView/index.jsx @@ -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'; diff --git a/src/containers/ListView/index.test.jsx b/src/containers/ListView/index.test.jsx index a6236ee..93540f1 100644 --- a/src/containers/ListView/index.test.jsx +++ b/src/containers/ListView/index.test.jsx @@ -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; diff --git a/src/containers/ResponseDisplay/index.jsx b/src/containers/ResponseDisplay/index.jsx index 3596ac6..441c643 100644 --- a/src/containers/ResponseDisplay/index.jsx +++ b/src/containers/ResponseDisplay/index.jsx @@ -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'; diff --git a/src/containers/ResponseDisplay/index.test.jsx b/src/containers/ResponseDisplay/index.test.jsx index 809aff9..e06723c 100644 --- a/src/containers/ResponseDisplay/index.test.jsx +++ b/src/containers/ResponseDisplay/index.test.jsx @@ -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 }), diff --git a/src/containers/ReviewActions/components/StartGradingButton.jsx b/src/containers/ReviewActions/components/StartGradingButton.jsx index 22105e5..4e26f96 100644 --- a/src/containers/ReviewActions/components/StartGradingButton.jsx +++ b/src/containers/ReviewActions/components/StartGradingButton.jsx @@ -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'; diff --git a/src/containers/ReviewActions/components/StartGradingButton.test.jsx b/src/containers/ReviewActions/components/StartGradingButton.test.jsx index 7e279b5..41eaa1a 100644 --- a/src/containers/ReviewActions/components/StartGradingButton.test.jsx +++ b/src/containers/ReviewActions/components/StartGradingButton.test.jsx @@ -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'); diff --git a/src/containers/ReviewActions/components/SubmissionNavigation.jsx b/src/containers/ReviewActions/components/SubmissionNavigation.jsx index f755737..510e84d 100644 --- a/src/containers/ReviewActions/components/SubmissionNavigation.jsx +++ b/src/containers/ReviewActions/components/SubmissionNavigation.jsx @@ -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'; /** diff --git a/src/containers/ReviewActions/components/SubmissionNavigation.test.jsx b/src/containers/ReviewActions/components/SubmissionNavigation.test.jsx index 417996b..6a23ad7 100644 --- a/src/containers/ReviewActions/components/SubmissionNavigation.test.jsx +++ b/src/containers/ReviewActions/components/SubmissionNavigation.test.jsx @@ -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', () => { diff --git a/src/containers/ReviewActions/index.jsx b/src/containers/ReviewActions/index.jsx index cc5b924..5b74130 100644 --- a/src/containers/ReviewActions/index.jsx +++ b/src/containers/ReviewActions/index.jsx @@ -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'; diff --git a/src/containers/ReviewActions/index.test.jsx b/src/containers/ReviewActions/index.test.jsx index ae395b8..38307a4 100644 --- a/src/containers/ReviewActions/index.test.jsx +++ b/src/containers/ReviewActions/index.test.jsx @@ -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'); diff --git a/src/containers/ReviewModal/index.jsx b/src/containers/ReviewModal/index.jsx index 0a160a7..53fed41 100644 --- a/src/containers/ReviewModal/index.jsx +++ b/src/containers/ReviewModal/index.jsx @@ -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'; diff --git a/src/containers/Rubric/Rubric.scss b/src/containers/Rubric/Rubric.scss index 15f1560..fd30b07 100644 --- a/src/containers/Rubric/Rubric.scss +++ b/src/containers/Rubric/Rubric.scss @@ -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); } diff --git a/src/containers/Rubric/RubricFeedback.jsx b/src/containers/Rubric/RubricFeedback.jsx index e5ccbeb..b580934 100644 --- a/src/containers/Rubric/RubricFeedback.jsx +++ b/src/containers/Rubric/RubricFeedback.jsx @@ -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'; diff --git a/src/containers/Rubric/RubricFeedback.test.jsx b/src/containers/Rubric/RubricFeedback.test.jsx index 38d1a5a..63a19c8 100644 --- a/src/containers/Rubric/RubricFeedback.test.jsx +++ b/src/containers/Rubric/RubricFeedback.test.jsx @@ -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, + })), }, })); diff --git a/src/containers/Rubric/__snapshots__/index.test.jsx.snap b/src/containers/Rubric/__snapshots__/index.test.jsx.snap index 5df0e64..7fc53d4 100644 --- a/src/containers/Rubric/__snapshots__/index.test.jsx.snap +++ b/src/containers/Rubric/__snapshots__/index.test.jsx.snap @@ -14,7 +14,9 @@ exports[`Rubric Container snapshot is grading 1`] = ` id="ora-grading.Rubric.rubric" /> -
+
-
+
(

-
+
{criteriaIndices.map((index) => ( '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, + })), }, })); diff --git a/src/data/actions/app.js b/src/data/actions/app.js deleted file mode 100644 index cbbc6df..0000000 --- a/src/data/actions/app.js +++ /dev/null @@ -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, -}); diff --git a/src/data/actions/app.test.js b/src/data/actions/app.test.js deleted file mode 100644 index a9a0a0f..0000000 --- a/src/data/actions/app.test.js +++ /dev/null @@ -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)); - }); -}); diff --git a/src/data/actions/grading.js b/src/data/actions/grading.js deleted file mode 100644 index 60395a4..0000000 --- a/src/data/actions/grading.js +++ /dev/null @@ -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, -}); diff --git a/src/data/actions/grading.test.js b/src/data/actions/grading.test.js deleted file mode 100644 index 07340f3..0000000 --- a/src/data/actions/grading.test.js +++ /dev/null @@ -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)); - }); -}); diff --git a/src/data/actions/index.js b/src/data/actions/index.js deleted file mode 100644 index 832a1e1..0000000 --- a/src/data/actions/index.js +++ /dev/null @@ -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, -}); diff --git a/src/data/actions/requests.js b/src/data/actions/requests.js deleted file mode 100644 index 108be74..0000000 --- a/src/data/actions/requests.js +++ /dev/null @@ -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, -}); diff --git a/src/data/actions/requests.test.js b/src/data/actions/requests.test.js deleted file mode 100644 index dfa1804..0000000 --- a/src/data/actions/requests.test.js +++ /dev/null @@ -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)); - }); -}); diff --git a/src/data/actions/submissions.js b/src/data/actions/submissions.js deleted file mode 100644 index 14f81d2..0000000 --- a/src/data/actions/submissions.js +++ /dev/null @@ -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, -}); diff --git a/src/data/actions/submissions.test.js b/src/data/actions/submissions.test.js deleted file mode 100644 index 75a92db..0000000 --- a/src/data/actions/submissions.test.js +++ /dev/null @@ -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)); - }); -}); diff --git a/src/data/actions/testUtils.js b/src/data/actions/testUtils.js deleted file mode 100644 index d7cc86c..0000000 --- a/src/data/actions/testUtils.js +++ /dev/null @@ -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, -}; diff --git a/src/data/actions/utils.js b/src/data/actions/utils.js deleted file mode 100644 index 78cd696..0000000 --- a/src/data/actions/utils.js +++ /dev/null @@ -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, -}; diff --git a/src/data/actions/utils.test.js b/src/data/actions/utils.test.js deleted file mode 100644 index b11085b..0000000 --- a/src/data/actions/utils.test.js +++ /dev/null @@ -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), - ); - }); - }); -}); diff --git a/src/data/reducers/app.js b/src/data/reducers/app.js deleted file mode 100644 index 87b239c..0000000 --- a/src/data/reducers/app.js +++ /dev/null @@ -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; diff --git a/src/data/reducers/grading.js b/src/data/reducers/grading.js deleted file mode 100644 index c7636ca..0000000 --- a/src/data/reducers/grading.js +++ /dev/null @@ -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: { - /** - * : { - * 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; diff --git a/src/data/reducers/index.js b/src/data/reducers/index.js deleted file mode 100755 index 8051e48..0000000 --- a/src/data/reducers/index.js +++ /dev/null @@ -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; diff --git a/src/data/reducers/requests.js b/src/data/reducers/requests.js deleted file mode 100644 index ed9b056..0000000 --- a/src/data/reducers/requests.js +++ /dev/null @@ -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; diff --git a/src/data/reducers/submissions.js b/src/data/reducers/submissions.js deleted file mode 100644 index 31d6e3a..0000000 --- a/src/data/reducers/submissions.js +++ /dev/null @@ -1,32 +0,0 @@ -import actions from 'data/actions'; - -const initialState = { - allSubmissions: { - /** - * : { - * 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; diff --git a/src/data/redux/app/index.js b/src/data/redux/app/index.js new file mode 100644 index 0000000..8abd5f9 --- /dev/null +++ b/src/data/redux/app/index.js @@ -0,0 +1,2 @@ +export { actions, reducer } from './reducer'; +export { default as selectors } from './selectors'; diff --git a/src/data/redux/app/reducer.js b/src/data/redux/app/reducer.js new file mode 100644 index 0000000..ad49cdf --- /dev/null +++ b/src/data/redux/app/reducer.js @@ -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, +}; diff --git a/src/data/selectors/app.js b/src/data/redux/app/selectors.js similarity index 97% rename from src/data/selectors/app.js rename to src/data/redux/app/selectors.js index adff91c..a6c3a4a 100644 --- a/src/data/selectors/app.js +++ b/src/data/redux/app/selectors.js @@ -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), }; diff --git a/src/data/selectors/app.test.js b/src/data/redux/app/selectors.test.js similarity index 95% rename from src/data/selectors/app.test.js rename to src/data/redux/app/selectors.test.js index 000b866..21efa38 100644 --- a/src/data/selectors/app.test.js +++ b/src/data/redux/app/selectors.test.js @@ -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; diff --git a/src/data/redux/grading/index.js b/src/data/redux/grading/index.js new file mode 100644 index 0000000..8abd5f9 --- /dev/null +++ b/src/data/redux/grading/index.js @@ -0,0 +1,2 @@ +export { actions, reducer } from './reducer'; +export { default as selectors } from './selectors'; diff --git a/src/data/redux/grading/reducer.js b/src/data/redux/grading/reducer.js new file mode 100644 index 0000000..60cec85 --- /dev/null +++ b/src/data/redux/grading/reducer.js @@ -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: { + /** + * : { + * 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, +}; diff --git a/src/data/selectors/grading.js b/src/data/redux/grading/selectors.js similarity index 98% rename from src/data/selectors/grading.js rename to src/data/redux/grading/selectors.js index 4827dee..3ac48ac 100644 --- a/src/data/selectors/grading.js +++ b/src/data/redux/grading/selectors.js @@ -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, diff --git a/src/data/redux/index.js b/src/data/redux/index.js new file mode 100644 index 0000000..93182cb --- /dev/null +++ b/src/data/redux/index.js @@ -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; diff --git a/src/data/redux/requests/index.js b/src/data/redux/requests/index.js new file mode 100644 index 0000000..27cac64 --- /dev/null +++ b/src/data/redux/requests/index.js @@ -0,0 +1 @@ +export { actions, reducer } from './reducer'; diff --git a/src/data/redux/requests/reducer.js b/src/data/redux/requests/reducer.js new file mode 100644 index 0000000..709eccb --- /dev/null +++ b/src/data/redux/requests/reducer.js @@ -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, +}; diff --git a/src/data/reducers/requests.test.js b/src/data/redux/requests/reducer.test.js similarity index 63% rename from src/data/reducers/requests.test.js rename to src/data/redux/requests/reducer.test.js index 2763b8c..4283473 100644 --- a/src/data/reducers/requests.test.js +++ b/src/data/redux/requests/reducer.test.js @@ -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 }, diff --git a/src/data/redux/submissions/index.js b/src/data/redux/submissions/index.js new file mode 100644 index 0000000..8abd5f9 --- /dev/null +++ b/src/data/redux/submissions/index.js @@ -0,0 +1,2 @@ +export { actions, reducer } from './reducer'; +export { default as selectors } from './selectors'; diff --git a/src/data/redux/submissions/reducer.js b/src/data/redux/submissions/reducer.js new file mode 100644 index 0000000..7dc9b9b --- /dev/null +++ b/src/data/redux/submissions/reducer.js @@ -0,0 +1,39 @@ +import { createSlice } from '@reduxjs/toolkit'; + +import { StrictDict } from 'utils'; + +const initialState = { + allSubmissions: { + /** + * : { + * 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, +}; diff --git a/src/data/selectors/submissions.js b/src/data/redux/submissions/selectors.js similarity index 100% rename from src/data/selectors/submissions.js rename to src/data/redux/submissions/selectors.js diff --git a/src/data/thunkActions/app.js b/src/data/redux/thunkActions/app.js similarity index 94% rename from src/data/thunkActions/app.js rename to src/data/redux/thunkActions/app.js index 4fb413c..cd5d067 100644 --- a/src/data/thunkActions/app.js +++ b/src/data/redux/thunkActions/app.js @@ -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'; diff --git a/src/data/thunkActions/app.test.js b/src/data/redux/thunkActions/app.test.js similarity index 97% rename from src/data/thunkActions/app.test.js rename to src/data/redux/thunkActions/app.test.js index 3ffeb9b..5c672bd 100644 --- a/src/data/thunkActions/app.test.js +++ b/src/data/redux/thunkActions/app.test.js @@ -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', () => ({ diff --git a/src/data/thunkActions/grading.js b/src/data/redux/thunkActions/grading.js similarity index 98% rename from src/data/thunkActions/grading.js rename to src/data/redux/thunkActions/grading.js index aa21244..18d8c53 100644 --- a/src/data/thunkActions/grading.js +++ b/src/data/redux/thunkActions/grading.js @@ -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'; diff --git a/src/data/thunkActions/grading.test.js b/src/data/redux/thunkActions/grading.test.js similarity index 94% rename from src/data/thunkActions/grading.test.js rename to src/data/redux/thunkActions/grading.test.js index 1fa8355..687acca 100644 --- a/src/data/thunkActions/grading.test.js +++ b/src/data/redux/thunkActions/grading.test.js @@ -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 })), }, })); diff --git a/src/data/thunkActions/index.js b/src/data/redux/thunkActions/index.js similarity index 100% rename from src/data/thunkActions/index.js rename to src/data/redux/thunkActions/index.js diff --git a/src/data/thunkActions/requests.js b/src/data/redux/thunkActions/requests.js similarity index 99% rename from src/data/thunkActions/requests.js rename to src/data/redux/thunkActions/requests.js index 61dd885..6462447 100644 --- a/src/data/thunkActions/requests.js +++ b/src/data/redux/thunkActions/requests.js @@ -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'; /** diff --git a/src/data/thunkActions/requests.test.js b/src/data/redux/thunkActions/requests.test.js similarity index 99% rename from src/data/thunkActions/requests.test.js rename to src/data/redux/thunkActions/requests.test.js index 2f1be66..e8dad93 100644 --- a/src/data/thunkActions/requests.test.js +++ b/src/data/redux/thunkActions/requests.test.js @@ -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'; diff --git a/src/data/thunkActions/testUtils.js b/src/data/redux/thunkActions/testUtils.js similarity index 100% rename from src/data/thunkActions/testUtils.js rename to src/data/redux/thunkActions/testUtils.js diff --git a/src/data/selectors/index.js b/src/data/selectors/index.js deleted file mode 100644 index eaaa4c6..0000000 --- a/src/data/selectors/index.js +++ /dev/null @@ -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, -}); diff --git a/src/data/store.js b/src/data/store.js index 43b278e..4e73ca3 100755 --- a/src/data/store.js +++ b/src/data/store.js @@ -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)), ); diff --git a/src/data/store.test.js b/src/data/store.test.js index 1333033..f44bc0f 100644 --- a/src/data/store.test.js +++ b/src/data/store.test.js @@ -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', () => { diff --git a/src/index.test.jsx b/src/index.test.jsx index d6212de..3b99601 100644 --- a/src/index.test.jsx +++ b/src/index.test.jsx @@ -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(), diff --git a/src/test/app.test.jsx b/src/test/app.test.jsx index d41285a..fa0c414 100644 --- a/src/test/app.test.jsx +++ b/src/test/app.test.jsx @@ -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 () => {