Merge pull request #11 from muselesscreator/override_flow

Override Grade workflow
This commit is contained in:
Ben Warzeski
2021-10-08 10:36:47 -04:00
committed by GitHub
13 changed files with 159 additions and 61 deletions

View File

@@ -8,11 +8,20 @@ exports[`ReviewActions component component snapshot: do not show rubric 1`] = `
<span
className="review-actions-username"
>
test-username
<span
className="lead"
>
test-username
</span>
<StatusBadge
className="review-actions-status"
className="review-actions-status mr-3"
status="grading-status"
/>
<span
className="small"
>
Score: 3/10
</span>
</span>
<div
className="review-actions-group"
@@ -31,7 +40,7 @@ exports[`ReviewActions component component snapshot: do not show rubric 1`] = `
</div>
`;
exports[`ReviewActions component component snapshot: show rubric 1`] = `
exports[`ReviewActions component component snapshot: show rubric, no score 1`] = `
<div>
<ActionRow
className="review-actions"
@@ -39,11 +48,18 @@ exports[`ReviewActions component component snapshot: show rubric 1`] = `
<span
className="review-actions-username"
>
test-username
<span
className="lead"
>
test-username
</span>
<StatusBadge
className="review-actions-status"
className="review-actions-status mr-3"
status="grading-status"
/>
<span
className="small"
/>
</span>
<div
className="review-actions-group"

View File

@@ -106,6 +106,7 @@ export class StartGradingButton extends React.Component {
isOpen={this.state.showConfirmStopGrading}
onCancel={this.hideConfirmStopGrading}
onConfirm={this.confirmStopGrading}
isOverride={this.props.gradeStatus === statuses.graded}
/>
</>
);
@@ -113,12 +114,14 @@ export class StartGradingButton extends React.Component {
}
StartGradingButton.propTypes = {
gradeStatus: PropTypes.string.isRequired,
gradingStatus: PropTypes.string.isRequired,
startGrading: PropTypes.func.isRequired,
stopGrading: PropTypes.func.isRequired,
};
export const mapStateToProps = (state) => ({
gradeStatus: selectors.grading.selected.gradeStatus(state),
gradingStatus: selectors.grading.selected.gradingStatus(state),
});

View File

@@ -23,6 +23,7 @@ jest.mock('data/selectors', () => ({
default: {
grading: {
selected: {
gradeStatus: (state) => ({ gradeStatus: state }),
gradingStatus: (state) => ({ gradingStatus: state }),
},
},
@@ -38,26 +39,43 @@ describe('StartGradingButton component', () => {
props.startGrading = jest.fn().mockName('this.props.startGrading');
props.stopGrading = jest.fn().mockName('this.props.stopGrading');
});
const render = (gradingStatus) => shallow(
<StartGradingButton {...props} gradingStatus={gradingStatus} />,
);
test('snapshot: locked (null)', () => {
el = render(statuses.locked);
expect(el).toMatchSnapshot();
expect(el).toEqual({});
});
test('snapshot: ungraded (startGrading callback)', () => {
expect(render(statuses.ungraded)).toMatchSnapshot();
});
test('snapshot: graded, confirmOverride (startGrading callback)', () => {
el = render(statuses.graded);
el.setState({ showConfirmOverrideGrade: true });
expect(el.instance().render()).toMatchSnapshot();
});
test('snapshot: inProgress, confirmStop (stopGrading callback)', () => {
el = render(statuses.inProgress);
el.setState({ showConfirmStopGrading: true });
expect(el.instance().render()).toMatchSnapshot();
describe('snapshotes', () => {
const mockedEl = (gradingStatus, gradeStatus) => {
const renderedEl = shallow(
<StartGradingButton
{...props}
gradingStatus={gradingStatus}
gradeStatus={gradeStatus || gradingStatus}
/>,
);
const mockMethod = (methodName) => {
renderedEl.instance()[methodName] = jest.fn().mockName(`this.${methodName}`);
};
mockMethod('handleClick');
mockMethod('hideConfirmOverrideGrade');
mockMethod('confirmOverrideGrade');
mockMethod('hideConfirmStopGrading');
mockMethod('confirmStopGrading');
return renderedEl;
};
test('snapshot: locked (null)', () => {
el = mockedEl(statuses.locked);
expect(el.instance().render()).toMatchSnapshot();
expect(el.isEmptyRender()).toEqual(true);
});
test('snapshot: ungraded (startGrading callback)', () => {
expect(mockedEl(statuses.ungraded).instance().render()).toMatchSnapshot();
});
test('snapshot: graded, confirmOverride (startGrading callback)', () => {
el = mockedEl(statuses.graded);
el.setState({ showConfirmOverrideGrade: true });
expect(el.instance().render()).toMatchSnapshot();
});
test('snapshot: inProgress, isOverride, confirmStop (stopGrading callback)', () => {
el = mockedEl(statuses.inProgress, statuses.graded);
el.setState({ showConfirmStopGrading: true });
expect(el.instance().render()).toMatchSnapshot();
});
});
});
describe('mapStateToProps', () => {
@@ -66,6 +84,9 @@ describe('StartGradingButton component', () => {
beforeEach(() => {
mapped = mapStateToProps(testState);
});
test('gradeStatus loads from grading.selected.gradeStatus', () => {
expect(mapped.gradeStatus).toEqual(selectors.grading.selected.gradeStatus(testState));
});
test('gradingStatus loads from grading.selected.gradingStatus', () => {
expect(mapped.gradingStatus).toEqual(selectors.grading.selected.gradingStatus(testState));
});

View File

@@ -5,14 +5,21 @@ import ConfirmModal from 'components/ConfirmModal';
export const StopGradingConfirmModal = ({
isOpen,
isOverride,
onCancel,
onConfirm,
}) => (
<ConfirmModal
title="Are you sure you want to stop grading this response?"
title={(isOverride
? 'Are you sure you want to stop grade override?'
: 'Are you sure you want to stop grading this response?'
)}
content="Your progress will be lost."
cancelText="Go back"
confirmText="Cancel Grading"
confirmText={(isOverride
? 'Stop grade override'
: 'Cancel grading'
)}
onCancel={onCancel}
onConfirm={onConfirm}
isOpen={isOpen}
@@ -20,6 +27,7 @@ export const StopGradingConfirmModal = ({
);
StopGradingConfirmModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
isOverride: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
};

View File

@@ -7,6 +7,7 @@ jest.mock('components/ConfirmModal', () => 'ConfirmModal');
describe('StopGradingConfirmModal', () => {
const props = {
isOpen: false,
isOverride: false,
onCancel: jest.fn().mockName('this.props.onCancel'),
onConfirm: jest.fn().mockName('this.props.onConfirm'),
};
@@ -16,4 +17,7 @@ describe('StopGradingConfirmModal', () => {
test('snapshot: open', () => {
expect(shallow(<StopGradingConfirmModal {...props} isOpen />)).toMatchSnapshot();
});
test('snapshot: open, isOverride', () => {
expect(shallow(<StopGradingConfirmModal {...props} isOverride />)).toMatchSnapshot();
});
});

View File

@@ -3,7 +3,6 @@ import { shallow } from 'enzyme';
import selectors from 'data/selectors';
import thunkActions from 'data/thunkActions';
import { gradingStatuses as statuses } from 'data/services/lms/constants';
import {
SubmissionNavigation,

View File

@@ -1,69 +1,72 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`StartGradingButton component component snapshot: graded, confirmOverride (startGrading callback) 1`] = `
exports[`StartGradingButton component component snapshotes snapshot: graded, confirmOverride (startGrading callback) 1`] = `
<React.Fragment>
<Button
iconAfter="Highlight"
onClick={[Function]}
onClick={[MockFunction this.handleClick]}
variant="primary"
>
Override grade
</Button>
<OverrideGradeConfirmModal
isOpen={true}
onCancel={[Function]}
onConfirm={[Function]}
onCancel={[MockFunction this.hideConfirmOverrideGrade]}
onConfirm={[MockFunction this.confirmOverrideGrade]}
/>
<StopGradingConfirmModal
isOpen={false}
onCancel={[Function]}
onConfirm={[Function]}
isOverride={true}
onCancel={[MockFunction this.hideConfirmStopGrading]}
onConfirm={[MockFunction this.confirmStopGrading]}
/>
</React.Fragment>
`;
exports[`StartGradingButton component component snapshot: inProgress, confirmStop (stopGrading callback) 1`] = `
exports[`StartGradingButton component component snapshotes snapshot: inProgress, isOverride, confirmStop (stopGrading callback) 1`] = `
<React.Fragment>
<Button
iconAfter="Cancel"
onClick={[Function]}
onClick={[MockFunction this.handleClick]}
variant="primary"
>
Stop grading this response
</Button>
<OverrideGradeConfirmModal
isOpen={false}
onCancel={[Function]}
onConfirm={[Function]}
onCancel={[MockFunction this.hideConfirmOverrideGrade]}
onConfirm={[MockFunction this.confirmOverrideGrade]}
/>
<StopGradingConfirmModal
isOpen={true}
onCancel={[Function]}
onConfirm={[Function]}
isOverride={true}
onCancel={[MockFunction this.hideConfirmStopGrading]}
onConfirm={[MockFunction this.confirmStopGrading]}
/>
</React.Fragment>
`;
exports[`StartGradingButton component component snapshot: locked (null) 1`] = `""`;
exports[`StartGradingButton component component snapshotes snapshot: locked (null) 1`] = `null`;
exports[`StartGradingButton component component snapshot: ungraded (startGrading callback) 1`] = `
<Fragment>
exports[`StartGradingButton component component snapshotes snapshot: ungraded (startGrading callback) 1`] = `
<React.Fragment>
<Button
iconAfter="Highlight"
onClick={[Function]}
onClick={[MockFunction this.handleClick]}
variant="primary"
>
Start Grading
</Button>
<OverrideGradeConfirmModal
isOpen={false}
onCancel={[Function]}
onConfirm={[Function]}
onCancel={[MockFunction this.hideConfirmOverrideGrade]}
onConfirm={[MockFunction this.confirmOverrideGrade]}
/>
<StopGradingConfirmModal
isOpen={false}
onCancel={[Function]}
onConfirm={[Function]}
isOverride={false}
onCancel={[MockFunction this.hideConfirmStopGrading]}
onConfirm={[MockFunction this.confirmStopGrading]}
/>
</Fragment>
</React.Fragment>
`;

View File

@@ -3,7 +3,7 @@
exports[`StopGradingConfirmModal snapshot: closed 1`] = `
<ConfirmModal
cancelText="Go back"
confirmText="Cancel Grading"
confirmText="Cancel grading"
content="Your progress will be lost."
isOpen={false}
onCancel={[MockFunction this.props.onCancel]}
@@ -15,7 +15,7 @@ exports[`StopGradingConfirmModal snapshot: closed 1`] = `
exports[`StopGradingConfirmModal snapshot: open 1`] = `
<ConfirmModal
cancelText="Go back"
confirmText="Cancel Grading"
confirmText="Cancel grading"
content="Your progress will be lost."
isOpen={true}
onCancel={[MockFunction this.props.onCancel]}
@@ -23,3 +23,15 @@ exports[`StopGradingConfirmModal snapshot: open 1`] = `
title="Are you sure you want to stop grading this response?"
/>
`;
exports[`StopGradingConfirmModal snapshot: open, isOverride 1`] = `
<ConfirmModal
cancelText="Go back"
confirmText="Stop grade override"
content="Your progress will be lost."
isOpen={false}
onCancel={[MockFunction this.props.onCancel]}
onConfirm={[MockFunction this.props.onConfirm]}
title="Are you sure you want to stop grade override?"
/>
`;

View File

@@ -17,14 +17,18 @@ import SubmissionNavigation from './components/SubmissionNavigation';
export const ReviewActions = ({
gradingStatus,
toggleShowRubric,
score: { pointsEarned, pointsPossible },
showRubric,
username,
}) => (
<div>
<ActionRow className="review-actions">
<span className="review-actions-username">
{username}
<StatusBadge className="review-actions-status" status={gradingStatus} />
<span className="lead">{username}</span>
<StatusBadge className="review-actions-status mr-3" status={gradingStatus} />
<span className="small">
{pointsPossible && `Score: ${pointsEarned}/${pointsPossible}`}
</span>
</span>
<div className="review-actions-group">
<Button variant="outline-primary" onClick={toggleShowRubric}>
@@ -39,6 +43,10 @@ export const ReviewActions = ({
ReviewActions.propTypes = {
gradingStatus: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
score: PropTypes.shape({
pointsEarned: PropTypes.number,
pointsPossible: PropTypes.number,
}).isRequired,
showRubric: PropTypes.bool.isRequired,
toggleShowRubric: PropTypes.func.isRequired,
};
@@ -46,6 +54,7 @@ ReviewActions.propTypes = {
export const mapStateToProps = (state) => ({
username: selectors.grading.selected.username(state),
gradingStatus: selectors.grading.selected.gradingStatus(state),
score: selectors.grading.selected.score(state),
showRubric: selectors.app.showRubric(state),
});

View File

@@ -16,8 +16,9 @@ jest.mock('data/selectors', () => ({
app: { showRubric: (state) => ({ showRubric: state }) },
grading: {
selected: {
username: (state) => ({ username: state }),
gradingStatus: (state) => ({ gradingStatus: state }),
score: (state) => ({ score: state }),
username: (state) => ({ username: state }),
},
},
},
@@ -32,6 +33,7 @@ describe('ReviewActions component', () => {
gradingStatus: 'grading-status',
username: 'test-username',
showRubric: false,
score: { pointsEarned: 3, pointsPossible: 10 },
};
beforeEach(() => {
props.toggleShowRubric = jest.fn().mockName('this.props.toggleShowRubric');
@@ -39,8 +41,8 @@ describe('ReviewActions component', () => {
test('snapshot: do not show rubric', () => {
expect(shallow(<ReviewActions {...props} />)).toMatchSnapshot();
});
test('snapshot: show rubric', () => {
expect(shallow(<ReviewActions {...props} showRubric />)).toMatchSnapshot();
test('snapshot: show rubric, no score', () => {
expect(shallow(<ReviewActions {...props} showRubric score={{}} />)).toMatchSnapshot();
});
});
describe('mapStateToProps', () => {
@@ -55,6 +57,9 @@ describe('ReviewActions component', () => {
test('gradingStatus loads from grading.selected.gradingStatus', () => {
expect(mapped.gradingStatus).toEqual(selectors.grading.selected.gradingStatus(testState));
});
test('score loads from grading.selected.score', () => {
expect(mapped.score).toEqual(selectors.grading.selected.score(testState));
});
test('showRubric loads from app.showRubric', () => {
expect(mapped.showRubric).toEqual(selectors.app.showRubric(testState));
});

View File

@@ -103,12 +103,20 @@ const app = createReducer(initialState, {
prev: state.current,
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: state.current,
current: { response: state.prev.response, ...payload },
gradeData: {
...state.gradeData,
[payload.submissionId]: payload.gradeData,
},
activeIndex: state.activeIndex - 1,
prev: null,
}),

View File

@@ -1,11 +1,8 @@
import { createSelector } from 'reselect';
import { feedbackRequirement } from 'data/services/lms/constants';
// import * in order to mock in-file references
import * as selectors from './app';
// import default export in order to test simpleSelectors not exported individually
import exportedSelectors from './app';
jest.mock('reselect', () => ({
createSelector: jest.fn((preSelectors, cb) => ({ preSelectors, cb })),

View File

@@ -33,8 +33,12 @@ export const selected = {};
* @return {string} selected submission id
*/
selected.submissionId = createSelector(
[module.simpleSelectors.selected, submissionsSelectors.allSubmissions],
(selectedIds, submissions) => submissions[selectedIds[0]].submissionId,
[
module.simpleSelectors.selected,
submissionsSelectors.allSubmissions,
module.simpleSelectors.activeIndex,
],
(selectedIds, submissions, activeIndex) => submissions[selectedIds[activeIndex]].submissionId,
);
/**
@@ -125,6 +129,15 @@ selected.criteriaGradeData = createSelector(
(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