From 2b585f2218b60c8262cf28fb62103aeb2d3746fb Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Fri, 5 Nov 2021 14:04:36 -0400 Subject: [PATCH] fix: remove old redux directories and update integration test --- .../ListView/ListViewBreadcrumb.jsx | 2 +- .../ListView/ListViewBreadcrumb.test.jsx | 15 +- src/data/actions/app.js | 19 -- src/data/actions/app.test.js | 22 -- src/data/actions/grading.js | 95 ------- src/data/actions/grading.test.js | 36 --- src/data/actions/index.js | 13 - src/data/actions/requests.js | 15 -- src/data/actions/requests.test.js | 18 -- src/data/actions/submissions.js | 15 -- src/data/actions/submissions.test.js | 14 -- src/data/actions/testUtils.js | 48 ---- src/data/actions/utils.js | 10 - src/data/actions/utils.test.js | 19 -- src/data/reducers/app.js | 43 ---- src/data/reducers/grading.js | 159 ------------ src/data/reducers/index.js | 16 -- src/data/reducers/requests.js | 41 ---- src/data/reducers/requests.test.js | 52 ---- src/data/reducers/submissions.js | 32 --- src/data/redux/index.js | 1 - src/data/selectors/app.js | 161 ------------ src/data/selectors/app.test.js | 232 ------------------ src/data/selectors/grading.js | 230 ----------------- src/data/selectors/index.js | 11 - src/data/selectors/submissions.js | 33 --- src/test/app.test.jsx | 12 +- 27 files changed, 14 insertions(+), 1350 deletions(-) delete mode 100644 src/data/actions/app.js delete mode 100644 src/data/actions/app.test.js delete mode 100644 src/data/actions/grading.js delete mode 100644 src/data/actions/grading.test.js delete mode 100644 src/data/actions/index.js delete mode 100644 src/data/actions/requests.js delete mode 100644 src/data/actions/requests.test.js delete mode 100644 src/data/actions/submissions.js delete mode 100644 src/data/actions/submissions.test.js delete mode 100644 src/data/actions/testUtils.js delete mode 100644 src/data/actions/utils.js delete mode 100644 src/data/actions/utils.test.js delete mode 100644 src/data/reducers/app.js delete mode 100644 src/data/reducers/grading.js delete mode 100755 src/data/reducers/index.js delete mode 100644 src/data/reducers/requests.js delete mode 100644 src/data/reducers/requests.test.js delete mode 100644 src/data/reducers/submissions.js delete mode 100644 src/data/selectors/app.js delete mode 100644 src/data/selectors/app.test.js delete mode 100644 src/data/selectors/grading.js delete mode 100644 src/data/selectors/index.js delete mode 100644 src/data/selectors/submissions.js 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/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 7cf2d8a..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: app.reducer, - grading: grading.reducer, - requests: requests.reducer, - submissions: submissions.reducer, -}); - -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/requests.test.js b/src/data/reducers/requests.test.js deleted file mode 100644 index 2763b8c..0000000 --- a/src/data/reducers/requests.test.js +++ /dev/null @@ -1,52 +0,0 @@ -import actions from 'data/actions'; -import { RequestStates } from 'data/constants/requests'; -import requests, { initialState } from './requests'; - -const testingState = { - ...initialState, - arbitraryField: 'arbitrary', -}; - -describe('requests reducer', () => { - it('has initial state', () => { - expect(requests(undefined, {})).toEqual(initialState); - }); - - const testValue = 'roll for initiative'; - const testKey = 'test-key'; - describe('handling actions', () => { - describe('requests.startRequest', () => { - it('adds a pending status for the given key', () => { - expect(requests( - testingState, - actions.requests.startRequest(testKey), - )).toEqual({ - ...testingState, - [testKey]: { status: RequestStates.pending }, - }); - }); - }); - describe('requests.completeRequest', () => { - it('adds a completed status with passed response', () => { - expect(requests( - testingState, - actions.requests.completeRequest({ requestKey: testKey, response: testValue }), - )).toEqual({ - ...testingState, - [testKey]: { status: RequestStates.completed, response: testValue }, - }); - }); - }); - describe('requests.failRequest', () => { - it('adds a failed status with passed error', () => { - expect(requests( - testingState, - actions.requests.failRequest({ requestKey: testKey, error: testValue }), - )).toEqual({ - ...testingState, - [testKey]: { status: RequestStates.failed, error: testValue }, - }); - }); - }); - }); -}); 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/index.js b/src/data/redux/index.js index 830765c..93182cb 100644 --- a/src/data/redux/index.js +++ b/src/data/redux/index.js @@ -21,7 +21,6 @@ const moduleProps = (propName) => Object.keys(modules).reduce( {}, ); -/* istanbul ignore next */ const rootReducer = combineReducers(moduleProps('reducer')); const actions = StrictDict(moduleProps('actions')); diff --git a/src/data/selectors/app.js b/src/data/selectors/app.js deleted file mode 100644 index adff91c..0000000 --- a/src/data/selectors/app.js +++ /dev/null @@ -1,161 +0,0 @@ -import { createSelector } from 'reselect'; - -import { feedbackRequirement } from 'data/services/lms/constants'; - -import { StrictDict } from 'utils'; - -import * as module from './app'; - -export const appSelector = (state) => state.app; - -const mkSimpleSelector = (cb) => createSelector([module.appSelector], cb); - -// top-level app data selectors -export const simpleSelectors = { - showReview: mkSimpleSelector(app => app.showReview), - showRubric: mkSimpleSelector(app => app.showRubric), - isGrading: mkSimpleSelector(app => app.isGrading), - courseMetadata: mkSimpleSelector(app => app.courseMetadata), - oraMetadata: mkSimpleSelector(app => app.oraMetadata), -}; - -export const courseId = ( - createSelector([module.simpleSelectors.courseMetadata], (data) => data.courseId) -); - -const oraMetadataSelector = (cb) => createSelector([module.simpleSelectors.oraMetadata], cb); -// ORA metadata selectors -export const ora = { - /** - * Returns the ORA name - * @return {string} - ORA name - */ - name: oraMetadataSelector(data => data.name), - /** - * Returns the ORA Prompt - * @return {string} - ORA prompt - */ - prompt: oraMetadataSelector(data => data.prompt), - /** - * Returns the ORA type - * @return {string} - ORA type (team vs individual) - */ - type: oraMetadataSelector(data => data.type), - /** - * Return file load response config - * @returns {string} - file load response config - */ - fileUploadResponseConfig: oraMetadataSelector(data => data.fileUploadResponseConfig), -}; - -/** - * Container for rubric config selectors - */ -export const rubric = {}; -/** - * Returns the full top-level rubric config from the ora metadata - * @return {object} - rubric config object - */ -rubric.config = oraMetadataSelector(data => data.rubricConfig); - -/** - * Returns a momoized selector depending on the rubric config with the given callback - * @param {func} cb - callback taking the rubric config as an arg, and returning a value - * @return {func} - a memoized selector that calls cb with the rubric config - */ -const rubricConfigSelector = (cb) => createSelector([module.rubric.config], cb); - -/** - * Returns true iff the rubric object has loaded. - * @return {bool} - has a rubric config been loaded? - */ -rubric.hasConfig = rubricConfigSelector(config => config !== undefined); -/** - * Returns the rubric-level feedback config string - * @return {string} - rubric-level feedback config string - */ -rubric.feedbackConfig = rubricConfigSelector(config => config.feedback); - -/** - * Return the criteria feedbase prompt - * @return {string} - criteria feedback prompt - */ -rubric.feedbackPrompt = rubricConfigSelector(config => config.feedbackPrompt); - -/** - * Returns a list of rubric criterion config objects for the ORA - * @return {obj[]} - array of criterion config objects - */ -rubric.criteria = rubricConfigSelector(config => config.criteria); - -/** - * Returns the config object for the rubric criterion at the given index (orderNum) - * @param {number} orderNum - rubric criterion index - * @return {obj} - criterion config object - */ -rubric.criterionConfig = (state, { orderNum }) => module.rubric.criteria(state)[orderNum]; - -/** - * Returns the feeback configuration string for tor the criterion at the given index - * (orderNum). - * @param {number} orderNum - rubric criterion index - * @return {string} - criterion feedback config string - */ -rubric.criterionFeedbackConfig = (state, { orderNum }) => ( - module.rubric.criterionConfig(state, { orderNum }).feedback -); - -/** - * Returns a list of rubric criteria indices for iterating over - * @return {number[]} - list of rubric criteria indices - */ -rubric.criteriaIndices = createSelector( - [module.rubric.criteria], - (rubricCriteria) => rubricCriteria.map(({ orderNum }) => orderNum), -); - -/** - * Returns true iff the passed feedback value is required or optional - * @return {bool} - should include feedback? - */ -const shouldIncludeFeedback = (feedback) => ([ - feedbackRequirement.required, - feedbackRequirement.optional, -]).includes(feedback); - -/** - * Returns an empty grade data object based on the rubric config loaded in the app model. - * @return {obj} - empty grade data object - */ -export const emptyGrade = createSelector( - [module.rubric.hasConfig, module.rubric.criteria, module.rubric.feedbackConfig], - (hasConfig, criteria, feedbackConfig) => { - if (!hasConfig) { - return null; - } - const gradeData = {}; - if (shouldIncludeFeedback(feedbackConfig)) { - gradeData.overallFeedback = ''; - } - gradeData.criteria = criteria.map(criterion => { - const entry = { - orderNum: criterion.orderNum, - name: criterion.name, - selectedOption: '', - }; - if (shouldIncludeFeedback(criterion.feedback)) { - entry.feedback = ''; - } - return entry; - }); - return gradeData; - }, -); - -export default StrictDict({ - ...simpleSelectors, - courseId, - ora, - rubric: StrictDict(rubric), - emptyGrade, -}); diff --git a/src/data/selectors/app.test.js b/src/data/selectors/app.test.js deleted file mode 100644 index 000b866..0000000 --- a/src/data/selectors/app.test.js +++ /dev/null @@ -1,232 +0,0 @@ -import { feedbackRequirement } from 'data/services/lms/constants'; - -// import * in order to mock in-file references -import * as selectors from './app'; - -jest.mock('reselect', () => ({ - createSelector: jest.fn((preSelectors, cb) => ({ preSelectors, cb })), -})); - -const testState = { - app: { - showReview: false, - showRubric: false, - isGrading: false, - courseMetadata: { - courseId: 'test-course-id', - }, - oraMetadata: { - name: 'test-ora-name', - prompt: 'test-ora-prompt', - type: 'test-ora-type', - rubricConfig: { - feedback: 'optional', - criteria: [ - { - orderNum: 0, - name: 'critERia0', - feedback: 'optional', - }, - { - orderNum: 1, - name: 'critEriA1', - feedback: 'disabled', - }, - { - orderNum: 2, - name: 'cRIteria2', - feedback: 'required', - }, - ], - }, - }, - }, -}; - -describe('app selectors unit tests', () => { - const { appSelector, simpleSelectors, rubric } = selectors; - describe('appSelector', () => { - it('returns the app data', () => { - expect(appSelector(testState)).toEqual(testState.app); - }); - }); - describe('simpleSelectors', () => { - const testSimpleSelector = (key) => { - const { preSelectors, cb } = simpleSelectors[key]; - expect(preSelectors).toEqual([appSelector]); - expect(cb(testState.app)).toEqual(testState.app[key]); - }; - test('simple selectors link their values from app store', () => { - [ - 'showReview', - 'showRubric', - 'isGrading', - 'courseMetadata', - 'oraMetadata', - ].map(testSimpleSelector); - }); - }); - const testReselect = ({ - selector, - preSelectors, - args, - expected, - }) => { - expect(selector.preSelectors).toEqual(preSelectors); - expect(selector.cb(args)).toEqual(expected); - }; - describe('courseId selector', () => { - it('returns course id from courseMetadata', () => { - testReselect({ - selector: selectors.courseId, - preSelectors: [simpleSelectors.courseMetadata], - args: testState.app.courseMetadata, - expected: testState.app.courseMetadata.courseId, - }); - }); - }); - describe('ora metadata selectors', () => { - const { oraMetadata } = testState.app; - const testOraSelector = (selector, expected) => ( - testReselect({ - selector, - preSelectors: [simpleSelectors.oraMetadata], - args: oraMetadata, - expected, - }) - ); - test('ora.name selector returns name from oraMetadata', () => { - testOraSelector(selectors.ora.name, oraMetadata.name); - }); - test('ora.prompt selector returns prompt from oraMetadata', () => { - testOraSelector(selectors.ora.prompt, oraMetadata.prompt); - }); - 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); - }); - }); - describe('rubricConfig selectors', () => { - const { rubricConfig } = testState.app.oraMetadata; - const testRubricSelector = (selector, expected, args = null) => ( - testReselect({ - selector, - preSelectors: [selectors.rubric.config], - args: args === null ? rubricConfig : args, - expected, - }) - ); - test('hasConfig', () => { - testReselect({ - selector: rubric.hasConfig, - preSelectors: [selectors.rubric.config], - args: rubricConfig, - expected: true, - }); - testReselect({ - selector: rubric.hasConfig, - preSelectors: [selectors.rubric.config], - args: undefined, - expected: false, - }); - }); - test('feedbackConfig', () => { - testRubricSelector(rubric.feedbackConfig, rubricConfig.feedback); - }); - test('criteria', () => { - testRubricSelector(rubric.criteria, rubricConfig.criteria); - }); - describe('criteria selectors', () => { - let criteria; - beforeEach(() => { - criteria = rubric.criteria; - rubric.criteria = jest.fn(({ app }) => app.oraMetadata.rubricConfig.criteria); - }); - afterEach(() => { - rubric.criteria = criteria; - }); - test('criterionConfig returns config by orderNum/index', () => { - const testCriterion = (orderNum) => { - expect( - rubric.criterionConfig(testState, { orderNum }), - ).toEqual(rubricConfig.criteria[orderNum]); - }; - [0, 1, 2].map(testCriterion); - }); - test('criterionFeedbackConfig', () => { - const testCriterion = (orderNum) => { - expect( - rubric.criterionFeedbackConfig(testState, { orderNum }), - ).toEqual(rubricConfig.criteria[orderNum].feedback); - }; - [0, 1, 2].map(testCriterion); - }); - test('criteriaIndices returns ordered list of orderNum values', () => { - testReselect({ - selector: rubric.criteriaIndices, - preSelectors: [criteria], - args: rubricConfig.criteria, - expected: [0, 1, 2], - }); - }); - }); - }); - describe('emptyGrade selector', () => { - const { rubricConfig } = testState.app.oraMetadata; - let preSelectors; - let cb; - beforeEach(() => { - ({ preSelectors, cb } = selectors.emptyGrade); - }); - it('is a memoized selector based on rubric.[hasConfig, criteria, feedbackConfig]', () => { - expect(preSelectors).toEqual([ - rubric.hasConfig, - rubric.criteria, - rubric.feedbackConfig, - ]); - }); - describe('If the config is not loaded (hasConfig = undefined)', () => { - it('returns null', () => { - expect(cb(false, {}, '')).toEqual(null); - }); - }); - describe('The generated object', () => { - it('loads an overallFeedback field iff feedbackConfig is optional or required', () => { - let gradeData = cb(true, rubricConfig.criteria, feedbackRequirement.optional); - expect(gradeData.overallFeedback).toEqual(''); - gradeData = cb(true, rubricConfig.criteria, feedbackRequirement.required); - expect(gradeData.overallFeedback).toEqual(''); - gradeData = cb(true, rubricConfig.criteria, feedbackRequirement.disabled); - expect(gradeData.overallFeedback).toEqual(undefined); - }); - it('loads criteria with feedback field based on requirement config', () => { - const gradeData = cb(true, rubricConfig.criteria, rubricConfig.feedback); - const { criteria } = rubricConfig; - expect(gradeData.criteria).toEqual([ - { - orderNum: criteria[0].orderNum, - name: criteria[0].name, - selectedOption: '', - feedback: '', - }, - { - orderNum: criteria[1].orderNum, - name: criteria[1].name, - selectedOption: '', - }, - { - orderNum: criteria[2].orderNum, - name: criteria[2].name, - selectedOption: '', - feedback: '', - }, - ]); - }); - }); - }); -}); diff --git a/src/data/selectors/grading.js b/src/data/selectors/grading.js deleted file mode 100644 index 4827dee..0000000 --- a/src/data/selectors/grading.js +++ /dev/null @@ -1,230 +0,0 @@ -import { createSelector } from 'reselect'; - -import { StrictDict } from 'utils'; -import { lockStatuses } from 'data/services/lms/constants'; -import submissionsSelectors from './submissions'; -import * as module from './grading'; - -export const simpleSelectors = { - selected: state => state.grading.selected, - activeIndex: state => state.grading.activeIndex, - current: state => state.grading.current, - gradeData: state => state.grading.gradeData, -}; - -/** - * returns the length of the list of selected submissions - * @return {number} selected submission list length - */ -export const selectionLength = createSelector( - [module.simpleSelectors.selected], - (selected) => selected.length, -); - -/************************************************** - * Selected Submission Selectors - **************************************************/ -export const selected = {}; - -/** - * returns the selected submission id - * Note: Not loaded from current as this is what sets that value in the - * (current) bucket - * @return {string} selected submission id - */ -selected.submissionId = createSelector( - [ - module.simpleSelectors.selected, - submissionsSelectors.allSubmissions, - module.simpleSelectors.activeIndex, - ], - (selectedIds, submissions, activeIndex) => submissions[selectedIds[activeIndex]].submissionId, -); - -/** - * Returns the grade status for the selected submission - * @return {string} grade status - */ -selected.gradeStatus = createSelector( - [module.simpleSelectors.current], - (current) => current.gradeStatus, -); - -/** - * Returns the lock status for the selected submission - * @return {string} lock status - */ -selected.lockStatus = createSelector( - [module.simpleSelectors.current], - (current) => current.lockStatus, -); - -/** - * Returns the response data for the selected submission - * @return {obj} response - * { text, files: [] } - */ -selected.response = createSelector( - [module.simpleSelectors.current], - (current) => current.response, -); - -/** - * Returns the "grading" status for the selected submission, - * which is a combination of the grade and lock statuses. - * @return {string} grading status - */ -selected.gradingStatus = createSelector( - [module.selected.gradeStatus, module.selected.lockStatus], - (gradeStatus, lockStatus) => (lockStatus === lockStatuses.unlocked ? gradeStatus : lockStatus), -); - -/*********************************** - * Selected Submission - Static Data - ***********************************/ - -/** - * returns static data from the active selected submission - * @return {obj} - staticData - * { submissionId, username, teamName, dateSubmitted } - */ -selected.staticData = createSelector( - [module.selected.submissionId, submissionsSelectors.allSubmissions], - (submissionId, allSubmissions) => { - const submission = allSubmissions[submissionId]; - const { grade, gradeStatus, ...staticData } = submission; - return staticData; - }, -); - -/** - * Returns the username for the selected submission - * @return {string} username - */ -selected.username = createSelector( - [module.selected.staticData], - (staticData) => staticData.username, -); - -/*********************************** - * Selected Submission - Grade Data - ***********************************/ - -/** - * Returns the grade data for the selected submission - * @return {obj} grade data - * { score, overallFeedback, criteria } - */ -selected.gradeData = createSelector( - [module.selected.submissionId, module.simpleSelectors.gradeData], - (submissionId, gradeData) => gradeData[submissionId], -); - -/** - * Returns list of criterion grade data for the current selection - * @return {obj[]} criterion grade data entries - */ -selected.criteriaGradeData = createSelector( - [module.selected.gradeData], - (data) => (data ? data.criteria : []), -); - -/** - * Returns the score object associated with the grade - * @return {obj} score object - */ -selected.score = createSelector( - [module.selected.gradeData], - (data) => ((data && data.score) ? data.score : {}), -); - -/** - * Returns the rubric-level feedback for the selected submission - * @return {string} selected submission's associated rubric-level feedback - */ -selected.overallFeedback = createSelector( - [module.selected.gradeData], - (data) => (data ? data.overallFeedback : ''), -); - -/** - * Returns the grade data for the given criterion of the current - * selection - * @param {number} orderNum - criterion orderNum (and index) - * @return {obj} - Grade Data associated with the criterion - */ -selected.criterionGradeData = (state, { orderNum }) => { - const data = module.selected.criteriaGradeData(state); - return data ? data[orderNum] : {}; -}; - -/** - * Returns the critierion-level feedback for the selected submission, given the - * orderNum of the criterion. - * @param {number} orderNum - criterion index - * @return {string} - criterion-level feedback response for the given criterion. - */ -selected.criterionFeedback = (state, { orderNum }) => { - const data = module.selected.criterionGradeData(state, { orderNum }); - return data ? data.feedback : ''; -}; - -/************************************************* - * Next/Previous Submission Selectors - *************************************************/ -const next = { - /** - * Returns true iff there exists a selection after the current selection - * in the queue. - * @return {bool} has next submission? - */ - doesExist: createSelector( - [simpleSelectors.selected, simpleSelectors.activeIndex], - (list, activeIndex) => activeIndex < list.length - 1, - ), - /** - * Returns the submissionId for the next submission in the selection queu - * @return {string} next submission id (null if there isn't one) - */ - submissionId: createSelector( - [simpleSelectors.selected, simpleSelectors.activeIndex], - (list, activeIndex) => { - if (activeIndex < list.length - 1) { - return list[activeIndex + 1]; - } - return null; - }, - ), -}; -const prev = { - /* - * Returns true iff there exists a selection previous to the current selection - * in the queue. - * @return {bool} has previous submission? - */ - doesExist: createSelector( - [simpleSelectors.activeIndex], - (activeIndex) => activeIndex > 0, - ), - /** - * Returns the submissionId for the previous submission in the selection queue - * @return {string} previous submission id (null if there isn't one) - */ - submissionId: createSelector( - [simpleSelectors.selected, simpleSelectors.activeIndex], - (list, activeIndex) => { - if (activeIndex > 0) { - return list[activeIndex - 1]; - } - return null; - }, - ), -}; - -export default StrictDict({ - ...simpleSelectors, - next: StrictDict(next), - prev: StrictDict(prev), - selected: StrictDict(selected), - selectionLength, -}); 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/selectors/submissions.js b/src/data/selectors/submissions.js deleted file mode 100644 index 63cd8a2..0000000 --- a/src/data/selectors/submissions.js +++ /dev/null @@ -1,33 +0,0 @@ -import _ from 'lodash'; -import { createSelector } from 'reselect'; - -import { StrictDict } from 'utils'; -import { lockStatuses } from 'data/services/lms/constants'; - -export const simpleSelectors = { - allSubmissions: state => state.submissions.allSubmissions, -}; - -/** - * Returns the submission list in default order for the table. - */ -export const listData = createSelector( - [simpleSelectors.allSubmissions], - (allSubmissions) => { - const submissionIds = Object.keys(allSubmissions); - const submissionList = submissionIds.map(id => { - const { gradeStatus, lockStatus, ...rest } = allSubmissions[id]; - const gradingStatus = (lockStatus === lockStatuses.unlocked ? gradeStatus : lockStatus); - return { gradingStatus, ...rest }; - }); - return _.sortBy( - submissionList, - ['submissionDate'], - ); - }, -); - -export default StrictDict({ - ...simpleSelectors, - listData, -}); diff --git a/src/test/app.test.jsx b/src/test/app.test.jsx index 74753b0..fa0c414 100644 --- a/src/test/app.test.jsx +++ b/src/test/app.test.jsx @@ -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 () => {