fix: remove old redux directories and update integration test
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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 }),
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
@@ -1,159 +0,0 @@
|
||||
import { createReducer } from '@reduxjs/toolkit';
|
||||
|
||||
import { lockStatuses } from 'data/services/lms/constants';
|
||||
import actions from 'data/actions';
|
||||
|
||||
const initialState = {
|
||||
selected: [
|
||||
/**
|
||||
* {
|
||||
* submissionId: '',
|
||||
* username: ''
|
||||
* teamName: ''
|
||||
* dateSubmitted: 0,
|
||||
* gradeStatus: '',
|
||||
* }
|
||||
*/
|
||||
],
|
||||
gradeData: {
|
||||
/**
|
||||
* <submissionId>: {
|
||||
* overallFeedback: '',
|
||||
* criteria: [{
|
||||
* orderNum: 0,
|
||||
* points: 0,
|
||||
* comments: '',
|
||||
* }],
|
||||
* }
|
||||
*/
|
||||
},
|
||||
activeIndex: null,
|
||||
current: {
|
||||
/**
|
||||
* gradeData: {
|
||||
* score: {
|
||||
* pointsEarned: 0,
|
||||
* pointsPossible: 0,
|
||||
* }
|
||||
* overallFeedback: '',
|
||||
* criteria: [{
|
||||
* name: '',
|
||||
* feedback: '',
|
||||
* selectedOption: '',
|
||||
* }],
|
||||
* }
|
||||
* gradeStatus: '',
|
||||
* response: {
|
||||
* text: '',
|
||||
* files: [{
|
||||
* download_url: '',
|
||||
* description: '',
|
||||
* name: '',
|
||||
* }],
|
||||
* },
|
||||
*/
|
||||
},
|
||||
prev: null, // { response }
|
||||
next: null, // { response }
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the given state's gradeData entry for the seleted submission,
|
||||
* overlaying the passed data on top of the existing data for the that
|
||||
* submission.
|
||||
* @return {object} - new state
|
||||
*/
|
||||
export const updateGradeData = (state, data) => ({
|
||||
...state,
|
||||
gradeData: {
|
||||
...state.gradeData,
|
||||
[state.current.submissionId]: {
|
||||
...state.gradeData[state.current.submissionId],
|
||||
...data,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates the given state's gradeData entry for the seleted submission,
|
||||
* overlaying the passed data on top of the existing data for the criterion
|
||||
* at the given index (orderNum) for the rubric.
|
||||
* @return {object} - new state
|
||||
*/
|
||||
export const updateCriterion = (state, orderNum, data) => {
|
||||
const entry = state.gradeData[state.current.submissionId];
|
||||
const criteria = {
|
||||
...entry.criteria,
|
||||
[orderNum]: { ...entry.criteria[orderNum], ...data },
|
||||
};
|
||||
return updateGradeData(state, { ...entry, criteria });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const app = createReducer(initialState, {
|
||||
[actions.grading.loadSubmission]: (state, { payload }) => ({
|
||||
...state,
|
||||
current: { ...payload },
|
||||
activeIndex: 0,
|
||||
}),
|
||||
[actions.grading.preloadNext]: (state, { payload }) => ({ ...state, next: payload }),
|
||||
[actions.grading.preloadPrev]: (state, { payload }) => ({ ...state, prev: payload }),
|
||||
[actions.grading.loadNext]: (state, { payload }) => ({
|
||||
...state,
|
||||
prev: { response: state.current.response },
|
||||
current: { response: state.next.response, ...payload },
|
||||
activeIndex: state.activeIndex + 1,
|
||||
gradeData: {
|
||||
...state.gradeData,
|
||||
[payload.submissionId]: payload.gradeData,
|
||||
},
|
||||
next: null,
|
||||
}),
|
||||
[actions.grading.loadPrev]: (state, { payload }) => ({
|
||||
...state,
|
||||
next: { response: state.current.response },
|
||||
current: { response: state.prev.response, ...payload },
|
||||
gradeData: {
|
||||
...state.gradeData,
|
||||
[payload.submissionId]: payload.gradeData,
|
||||
},
|
||||
activeIndex: state.activeIndex - 1,
|
||||
prev: null,
|
||||
}),
|
||||
[actions.grading.updateSelection]: (state, { payload }) => ({
|
||||
...state,
|
||||
selected: payload,
|
||||
activeIndex: 0,
|
||||
}),
|
||||
[actions.grading.startGrading]: (state, { payload }) => updateGradeData(
|
||||
{
|
||||
...state,
|
||||
current: { ...state.current, lockStatus: lockStatuses.inProgress },
|
||||
},
|
||||
{ ...payload },
|
||||
),
|
||||
[actions.grading.setRubricFeedback]: (state, { payload }) => (
|
||||
updateGradeData(state, { overallFeedback: payload })
|
||||
),
|
||||
[actions.grading.setCriterionOption]: (state, { payload: { orderNum, value } }) => (
|
||||
updateCriterion(state, orderNum, { selectedOption: value })
|
||||
),
|
||||
[actions.grading.setCriterionFeedback]: (state, { payload: { orderNum, value } }) => (
|
||||
updateCriterion(state, orderNum, { feedback: value })
|
||||
),
|
||||
[actions.grading.clearGrade]: (state) => {
|
||||
const gradeData = { ...state.gradeData };
|
||||
delete gradeData[state.current.submissionId];
|
||||
return {
|
||||
...state,
|
||||
gradeData,
|
||||
current: {
|
||||
...state.current,
|
||||
lockStatus: lockStatuses.unlocked,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export { initialState };
|
||||
export default app;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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 },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import actions from 'data/actions';
|
||||
|
||||
const initialState = {
|
||||
allSubmissions: {
|
||||
/**
|
||||
* <submissionId>: {
|
||||
* submissionId: '',
|
||||
* username: ''
|
||||
* teamName: ''
|
||||
* dateSubmitted: 0,
|
||||
* gradeStatus: ''
|
||||
* grade: {
|
||||
* pointsEarned: 0,
|
||||
* pointsPossible: 0,
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const grades = (state = initialState, { type, payload }) => {
|
||||
switch (type) {
|
||||
case actions.submissions.loadList.toString():
|
||||
return { ...state, allSubmissions: payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export { initialState };
|
||||
export default grades;
|
||||
@@ -21,7 +21,6 @@ const moduleProps = (propName) => Object.keys(modules).reduce(
|
||||
{},
|
||||
);
|
||||
|
||||
/* istanbul ignore next */
|
||||
const rootReducer = combineReducers(moduleProps('reducer'));
|
||||
|
||||
const actions = StrictDict(moduleProps('actions'));
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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: '',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user