From a9ce6ac5387f01921133aca0a44b99c9ac496d64 Mon Sep 17 00:00:00 2001 From: Jhon Vente Date: Mon, 9 Oct 2023 08:27:06 -0500 Subject: [PATCH 01/24] feat: grading details UI problem steps --- src/components/StatusBadge.jsx | 6 +- src/components/StatusBadge.test.jsx | 10 +- src/containers/ListView/ListView.scss | 9 + src/containers/ListView/SubmissionsTable.jsx | 93 +++++- .../ListView/SubmissionsTable.test.jsx | 28 +- .../__snapshots__/index.test.jsx.snap | 4 + src/containers/ListView/index.jsx | 2 + src/containers/ListView/index.test.jsx | 1 + src/containers/ListView/messages.js | 40 +++ src/containers/ResponseDisplay/index.test.jsx | 2 +- .../ReviewProblemStepsContent.jsx | 55 ++++ .../ReviewProblemStepsContent.test.jsx | 60 ++++ .../ReviewProblemStepsModal.scss | 44 +++ .../ReviewProblemStepsContent.test.jsx.snap | 56 ++++ .../components/CloseReviewConfirmModal.jsx | 33 +++ .../CloseReviewConfirmModal.test.jsx | 24 ++ .../ReviewProblemStepActions.scss | 37 +++ .../ReviewProblemStepActions/index.jsx | 70 +++++ .../ReviewProblemStepActions/messages.js | 21 ++ .../ReviewProblemStepsContent.scss | 6 + .../AssessmentsTable/AssessmentsTable.scss | 14 + .../components/AssessmentsTable/constants.js | 91 ++++++ .../components/AssessmentsTable/index.jsx | 117 ++++++++ .../AssessmentsTable/index.test.jsx | 11 + .../components/AssessmentsTable/messages.js | 21 ++ .../components/OverrideGradeConfirmModal.jsx | 33 +++ .../OverrideGradeConfirmModal.test.jsx | 21 ++ .../ResponsesList/ResponseList.scss | 9 + .../components/ResponseItem/index.jsx | 31 ++ .../components/ResponsesList/index.jsx | 93 ++++++ .../components/ResponsesList/index.test.jsx | 10 + .../components/ResponsesList/messages.js | 21 ++ .../__snapshots__/index.test.jsx.snap | 18 ++ .../components/StartGradingButton/hooks.js | 141 +++++++++ .../StartGradingButton/hooks.test.js | 280 ++++++++++++++++++ .../components/StartGradingButton/index.jsx | 38 +++ .../StartGradingButton/index.test.jsx | 52 ++++ .../components/StartGradingButton/messages.js | 21 ++ .../components/StopGradingConfirmModal.jsx | 39 +++ .../StopGradingConfirmModal.test.jsx | 25 ++ .../OverrideGradeConfirmModal.test.jsx.snap | 25 ++ .../StopGradingConfirmModal.test.jsx.snap | 37 +++ .../components/messages.js | 81 +++++ .../ReviewProblemStepsContent/index.jsx | 30 ++ .../ReviewProblemStepsContent/index.test.jsx | 42 +++ .../CloseReviewConfirmModal.test.jsx.snap | 25 ++ .../components/messages.js | 26 ++ .../ReviewProblemStepsModal/hooks.js | 59 ++++ .../ReviewProblemStepsModal/hooks.test.js | 151 ++++++++++ .../ReviewProblemStepsModal/index.jsx | 54 ++++ .../ReviewProblemStepsModal/index.test.jsx | 153 ++++++++++ .../ReviewProblemStepsModal/messages.js | 17 ++ src/data/constants/app.test.js | 44 ++- src/data/redux/grading/reducer.js | 4 + src/data/redux/index.js | 2 + src/data/redux/problem-steps/index.js | 2 + src/data/redux/problem-steps/reducer.js | 24 ++ src/data/redux/problem-steps/selectors.js | 9 + src/data/redux/thunkActions/grading.js | 18 +- src/data/services/lms/constants.js | 1 + webpack.dev-tutor.config.js | 0 61 files changed, 2445 insertions(+), 46 deletions(-) create mode 100644 src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.jsx create mode 100644 src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.test.jsx create mode 100644 src/containers/ReviewProblemStepsModal/ReviewProblemStepsModal.scss create mode 100644 src/containers/ReviewProblemStepsModal/__snapshots__/ReviewProblemStepsContent.test.jsx.snap create mode 100644 src/containers/ReviewProblemStepsModal/components/CloseReviewConfirmModal.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/CloseReviewConfirmModal.test.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/ReviewProblemStepActions.scss create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/messages.js create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/ReviewProblemStepsContent.scss create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/AssessmentsTable.scss create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/constants.js create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/messages.js create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/OverrideGradeConfirmModal.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/OverrideGradeConfirmModal.test.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/ResponseList.scss create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/messages.js create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/__snapshots__/index.test.jsx.snap create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/hooks.js create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/hooks.test.js create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/index.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/index.test.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/messages.js create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StopGradingConfirmModal.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StopGradingConfirmModal.test.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/__snapshots__/OverrideGradeConfirmModal.test.jsx.snap create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/__snapshots__/StopGradingConfirmModal.test.jsx.snap create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/messages.js create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.test.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/__snapshots__/CloseReviewConfirmModal.test.jsx.snap create mode 100644 src/containers/ReviewProblemStepsModal/components/messages.js create mode 100644 src/containers/ReviewProblemStepsModal/hooks.js create mode 100644 src/containers/ReviewProblemStepsModal/hooks.test.js create mode 100644 src/containers/ReviewProblemStepsModal/index.jsx create mode 100644 src/containers/ReviewProblemStepsModal/index.test.jsx create mode 100644 src/containers/ReviewProblemStepsModal/messages.js create mode 100644 src/data/redux/problem-steps/index.js create mode 100644 src/data/redux/problem-steps/reducer.js create mode 100644 src/data/redux/problem-steps/selectors.js create mode 100755 webpack.dev-tutor.config.js diff --git a/src/components/StatusBadge.jsx b/src/components/StatusBadge.jsx index 021a8850..a240d2eb 100644 --- a/src/components/StatusBadge.jsx +++ b/src/components/StatusBadge.jsx @@ -25,7 +25,7 @@ export const statusVariants = StrictDict({ /** * */ -export const StatusBadge = ({ className, status }) => { +export const StatusBadge = ({ className, status, title }) => { if (!Object.keys(statusVariants).includes(status)) { return null; } @@ -34,16 +34,18 @@ export const StatusBadge = ({ className, status }) => { className={className} variant={statusVariants[status]} > - + { title || } ); }; StatusBadge.defaultProps = { className: '', + title: '', }; StatusBadge.propTypes = { className: PropTypes.string, status: PropTypes.string.isRequired, + title: PropTypes.string, }; export default StatusBadge; diff --git a/src/components/StatusBadge.test.jsx b/src/components/StatusBadge.test.jsx index fd71f7f9..9f461958 100644 --- a/src/components/StatusBadge.test.jsx +++ b/src/components/StatusBadge.test.jsx @@ -6,13 +6,21 @@ import { StatusBadge } from './StatusBadge'; const className = 'test-className'; describe('StatusBadge component', () => { - const render = (status) => shallow(); + const render = (status, title) => shallow(); describe('behavior', () => { it('does not render if status does not have configured variant', () => { const el = render('arbitrary'); expect(el.snapshot).toMatchSnapshot(); expect(el.isEmptyRender()).toEqual(true); }); + it('renders the title when title prop is passed', () => { + const title = 'Custom Title'; + const wrapper = render('graded', title); + + expect(wrapper.find('Badge').exists()).toBe(true); + expect(wrapper.find('Badge').prop('variant')).toBe('success'); + expect(wrapper.text()).toContain(title); + }); describe('status snapshots: loads badge with configured variant and message.', () => { test('`ungraded` shows primary button variant and message', () => { const el = render(gradingStatuses.ungraded); diff --git a/src/containers/ListView/ListView.scss b/src/containers/ListView/ListView.scss index 55096220..8881a848 100644 --- a/src/containers/ListView/ListView.scss +++ b/src/containers/ListView/ListView.scss @@ -25,4 +25,13 @@ span.pgn__icon.breadcrumb-arrow { margin-bottom: 0; } } + .step-problems-button-badge { + margin-right: 8px; + padding: 0px !important; + } + .btn-view-details { + padding-left: 0px; + text-decoration: underline; + font-weight: bold; + } } diff --git a/src/containers/ListView/SubmissionsTable.jsx b/src/containers/ListView/SubmissionsTable.jsx index ee76b024..e5c08a5d 100644 --- a/src/containers/ListView/SubmissionsTable.jsx +++ b/src/containers/ListView/SubmissionsTable.jsx @@ -7,13 +7,15 @@ import { DataTable, TextFilter, MultiSelectDropdownFilter, -} from '@openedx/paragon'; + Button, + Hyperlink, +} from '@edx/paragon'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { gradingStatuses, submissionFields } from 'data/services/lms/constants'; import lmsMessages from 'data/services/lms/messages'; -import { selectors, thunkActions } from 'data/redux'; +import { selectors, thunkActions, actions } from 'data/redux'; import StatusBadge from 'components/StatusBadge'; import FilterStatusComponent from './FilterStatusComponent'; @@ -22,19 +24,27 @@ import SelectedBulkAction from './SelectedBulkAction'; import messages from './messages'; +const problemSteps = { + problemStepsTraining: true, + problemStepsPeers: false, + problemStepsSelf: true, + problemStepsStaff: true, +}; /** * */ export class SubmissionsTable extends React.Component { get gradeStatusOptions() { - return Object.keys(gradingStatuses).map(statusKey => ({ + return Object.keys(gradingStatuses).map((statusKey) => ({ name: this.translate(lmsMessages[gradingStatuses[statusKey]]), value: gradingStatuses[statusKey], })); } get userLabel() { - return this.translate(this.props.isIndividual ? messages.username : messages.teamName); + return this.translate( + this.props.isIndividual ? messages.username : messages.teamName, + ); } get userAccessor() { @@ -54,11 +64,60 @@ export class SubmissionsTable extends React.Component { return date.toLocaleString(); }; - formatGrade = ({ value: score }) => ( - score === null ? '-' : `${score.pointsEarned}/${score.pointsPossible}` + formatGrade = ({ value: score }) => (score === null ? '-' : `${score.pointsEarned}/${score.pointsPossible}`); + + formatStatus = ({ value }) => ; + + handleProblemStepClick = (problemStepType) => { + console.log(problemStepType); + }; + + formatProblemStepsStatus = () => { + const stepProblems = Object.keys(problemSteps); + return ( +
+ {stepProblems.map((stepProblem) => ( + + ))} +
+ ); + }; + + handleProblemStepsDetailClick = (data, currentRow) => { + const submissionUUIDs = data.map((row) => row.submissionUUID); + const submissionId = currentRow.original.submissionUUID; + const currentRowIndex = submissionUUIDs.indexOf(submissionId); + this.props.loadSelectionForReview(submissionUUIDs, false, submissionId); + this.props.setActiveSubmissionIndex(currentRowIndex); + this.props.setProblemStepsModal(true); + }; + + problemStepsViewDetails = ({ data, row: currentRow }) => ( + ); - formatStatus = ({ value }) => (); + emailAddressCell = () => ( + + email@example.com + + ); translate = (...args) => this.props.intl.formatMessage(...args); @@ -96,6 +155,11 @@ export class SubmissionsTable extends React.Component { Header: this.userLabel, accessor: this.userAccessor, }, + { + Header: this.translate(messages.emailLabel), + accessor: null, + Cell: this.emailAddressCell, + }, { Header: this.dateSubmittedLabel, accessor: submissionFields.dateSubmitted, @@ -116,6 +180,17 @@ export class SubmissionsTable extends React.Component { filter: 'includesValue', filterChoices: this.gradeStatusOptions, }, + { + Header: this.translate(messages.problemSteps), + Cell: this.formatProblemStepsStatus, + }, + ]} + additionalColumns={[ + { + id: 'action', + Header: this.translate(messages.action), + Cell: this.problemStepsViewDetails, + }, ]} > @@ -144,6 +219,8 @@ SubmissionsTable.propTypes = { }), })), loadSelectionForReview: PropTypes.func.isRequired, + setProblemStepsModal: PropTypes.func.isRequired, + setActiveSubmissionIndex: PropTypes.func.isRequired, }; export const mapStateToProps = (state) => ({ @@ -153,6 +230,8 @@ export const mapStateToProps = (state) => ({ export const mapDispatchToProps = { loadSelectionForReview: thunkActions.grading.loadSelectionForReview, + setProblemStepsModal: actions.problemSteps.setOpenReviewModal, + setActiveSubmissionIndex: actions.grading.setActiveIndex, }; export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(SubmissionsTable)); diff --git a/src/containers/ListView/SubmissionsTable.test.jsx b/src/containers/ListView/SubmissionsTable.test.jsx index ba24d6f8..5fe447d0 100644 --- a/src/containers/ListView/SubmissionsTable.test.jsx +++ b/src/containers/ListView/SubmissionsTable.test.jsx @@ -38,6 +38,14 @@ jest.mock('data/redux', () => ({ loadSelectionForReview: (...args) => ({ loadSelectionForReview: args }), }, }, + actions: { + problemSteps: { + reviewModalOpen: (...args) => ({ reviewModalOpen: args }), + }, + grading: { + setActiveIndex: (...args) => ({ setActiveIndex: args }), + }, + }, })); let el; @@ -117,6 +125,8 @@ describe('SubmissionsTable component', () => { }; beforeEach(() => { props.loadSelectionForReview = jest.fn(); + props.setProblemStepsModal = jest.fn(); + props.setActiveSubmissionIndex = jest.fn(); props.intl = { formatMessage }; }); describe('render tests', () => { @@ -129,9 +139,14 @@ describe('SubmissionsTable component', () => { describe('snapshots', () => { beforeEach(() => { mockMethod('handleViewAllResponsesClick'); + mockMethod('handleProblemStepsDetailClick'); + mockMethod('handleProblemStepClick'); mockMethod('formatDate'); mockMethod('formatGrade'); mockMethod('formatStatus'); + mockMethod('emailAddressCell'); + mockMethod('formatProblemStepsStatus'); + mockMethod('problemStepsViewDetails'); }); test('snapshot: empty (no list data)', () => { el = shallow(); @@ -168,6 +183,7 @@ describe('SubmissionsTable component', () => { beforeEach(() => { columns = tableProps.columns; }); + test('username column', () => { expect(columns[0]).toEqual({ Header: messages.username.defaultMessage, @@ -175,7 +191,7 @@ describe('SubmissionsTable component', () => { }); }); test('submission date column', () => { - expect(columns[1]).toEqual({ + expect(columns[2]).toEqual({ Header: messages.learnerSubmissionDate.defaultMessage, accessor: submissionFields.dateSubmitted, Cell: el.instance.children[0].props.columns[1].Cell, @@ -183,7 +199,7 @@ describe('SubmissionsTable component', () => { }); }); test('grade column', () => { - expect(columns[2]).toEqual({ + expect(columns[3]).toEqual({ Header: messages.grade.defaultMessage, accessor: submissionFields.score, Cell: el.instance.children[0].props.columns[2].Cell, @@ -191,7 +207,7 @@ describe('SubmissionsTable component', () => { }); }); test('grading status column', () => { - expect(columns[3]).toEqual({ + expect(columns[4]).toEqual({ Header: messages.gradingStatus.defaultMessage, accessor: submissionFields.gradingStatus, Cell: el.instance.children[0].props.columns[3].Cell, @@ -214,7 +230,7 @@ describe('SubmissionsTable component', () => { }); }); test('submission date column', () => { - expect(columns[1]).toEqual({ + expect(columns[2]).toEqual({ Header: messages.teamSubmissionDate.defaultMessage, accessor: submissionFields.dateSubmitted, Cell: el.instance.children[0].props.columns[1].Cell, @@ -222,7 +238,7 @@ describe('SubmissionsTable component', () => { }); }); test('grade column', () => { - expect(columns[2]).toEqual({ + expect(columns[3]).toEqual({ Header: messages.grade.defaultMessage, accessor: submissionFields.score, Cell: el.instance.children[0].props.columns[2].Cell, @@ -230,7 +246,7 @@ describe('SubmissionsTable component', () => { }); }); test('grading status column', () => { - expect(columns[3]).toEqual({ + expect(columns[4]).toEqual({ Header: messages.gradingStatus.defaultMessage, accessor: submissionFields.gradingStatus, Cell: el.instance.children[0].props.columns[3].Cell, diff --git a/src/containers/ListView/__snapshots__/index.test.jsx.snap b/src/containers/ListView/__snapshots__/index.test.jsx.snap index d7d153c5..732de8b0 100644 --- a/src/containers/ListView/__snapshots__/index.test.jsx.snap +++ b/src/containers/ListView/__snapshots__/index.test.jsx.snap @@ -6,6 +6,7 @@ exports[`ListView component component snapshots error 1`] = ` > + `; @@ -18,6 +19,7 @@ exports[`ListView component component snapshots loaded has data 1`] = ` + `; @@ -29,6 +31,7 @@ exports[`ListView component component snapshots loaded with no data 1`] = ` courseId="test-course-id" /> + `; @@ -52,5 +55,6 @@ exports[`ListView component component snapshots loading 1`] = ` + `; diff --git a/src/containers/ListView/index.jsx b/src/containers/ListView/index.jsx index d0a8b29e..ba57f2e7 100644 --- a/src/containers/ListView/index.jsx +++ b/src/containers/ListView/index.jsx @@ -9,6 +9,7 @@ import { selectors, thunkActions } from 'data/redux'; import { RequestKeys } from 'data/constants/requests'; import ReviewModal from 'containers/ReviewModal'; +import ReviewProblemStepsModal from 'containers/ReviewProblemStepsModal'; import ListError from './ListError'; import ListViewBreadcrumb from './ListViewBreadcrumb'; @@ -51,6 +52,7 @@ export class ListView extends React.Component { )} + ); } diff --git a/src/containers/ListView/index.test.jsx b/src/containers/ListView/index.test.jsx index 3e0845c1..0066ea1f 100644 --- a/src/containers/ListView/index.test.jsx +++ b/src/containers/ListView/index.test.jsx @@ -9,6 +9,7 @@ import { ListView, mapStateToProps, mapDispatchToProps } from '.'; jest.mock('components/StatusBadge', () => 'StatusBadge'); jest.mock('containers/ReviewModal', () => 'ReviewModal'); +jest.mock('containers/ReviewProblemStepsModal', () => 'ReviewProblemStepsModal'); jest.mock('./ListViewBreadcrumb', () => 'ListViewBreadcrumb'); jest.mock('./ListError', () => 'ListError'); jest.mock('./SubmissionsTable', () => 'SubmissionsTable'); diff --git a/src/containers/ListView/messages.js b/src/containers/ListView/messages.js index cc99ffbe..a16d9757 100644 --- a/src/containers/ListView/messages.js +++ b/src/containers/ListView/messages.js @@ -81,6 +81,46 @@ const messages = defineMessages({ defaultMessage: 'Loading responses', description: 'loading text for submission response list', }, + emailLabel: { + id: 'ora-grading.ListView.emailLabel', + defaultMessage: 'Email', + description: 'email column text for submission response list', + }, + problemSteps: { + id: 'ora-grading.ListView.problemSteps', + defaultMessage: 'Problem Steps', + description: 'problem steps column text for submission response list', + }, + problemStepsTraining: { + id: 'ora-grading.ListView.problemSteps', + defaultMessage: 'Training', + description: 'problem step training badge text in column problemSteps for submission response list', + }, + problemStepsPeers: { + id: 'ora-grading.ListView.problemSteps', + defaultMessage: 'Peer', + description: 'problem step peer badge text in column problemSteps for submission response list', + }, + problemStepsSelf: { + id: 'ora-grading.ListView.problemSteps', + defaultMessage: 'Self', + description: 'problem step training badge text in column problemSteps for submission response list', + }, + problemStepsStaff: { + id: 'ora-grading.ListView.problemSteps', + defaultMessage: 'Staff', + description: 'problem step training badge text in column problemSteps for submission response list', + }, + action: { + id: 'ora-grading.ListView.action', + defaultMessage: 'Action', + description: 'action column text for submission response list', + }, + actionDetail: { + id: 'ora-grading.ListView.action', + defaultMessage: 'View details', + description: 'view detail text for submission response list', + }, }); export default messages; diff --git a/src/containers/ResponseDisplay/index.test.jsx b/src/containers/ResponseDisplay/index.test.jsx index 797124db..266e61d4 100644 --- a/src/containers/ResponseDisplay/index.test.jsx +++ b/src/containers/ResponseDisplay/index.test.jsx @@ -51,7 +51,7 @@ describe('ResponseDisplay', () => { }; let el; beforeAll(() => { - global.window = {}; + global.window ??= Object.create(window); }); beforeEach(() => { el = shallow(); diff --git a/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.jsx b/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.jsx new file mode 100644 index 00000000..b50f0175 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +import { Col, Row } from '@edx/paragon'; + +import { selectors } from 'data/redux'; +import { RequestKeys } from 'data/constants/requests'; + +import ResponseDisplay from 'containers/ResponseDisplay'; +import Rubric from 'containers/Rubric'; +import ReviewErrors from 'containers/ReviewModal/ReviewErrors'; + +/** + * + */ +export const ReviewProblemStepsContent = ({ isFailed, isLoaded, showRubric }) => (isLoaded || isFailed) && ( +
+
+ + {isLoaded && ( + + + + + {showRubric && } + + )} +
+
+); +ReviewProblemStepsContent.defaultProps = { + isFailed: false, + isLoaded: false, + showRubric: false, +}; +ReviewProblemStepsContent.propTypes = { + isFailed: PropTypes.bool, + isLoaded: PropTypes.bool, + showRubric: PropTypes.bool, +}; + +export const mapStateToProps = (state) => ({ + isFailed: selectors.requests.isFailed(state, { + requestKey: RequestKeys.fetchSubmission, + }), + isLoaded: selectors.requests.isCompleted(state, { + requestKey: RequestKeys.fetchSubmission, + }), + showRubric: selectors.app.showRubric(state), +}); + +export const mapDispatchToProps = {}; + +export default connect(mapStateToProps, mapDispatchToProps)(ReviewProblemStepsContent); diff --git a/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.test.jsx b/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.test.jsx new file mode 100644 index 00000000..c7310f62 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.test.jsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { selectors } from 'data/redux'; +import { RequestKeys } from 'data/constants/requests'; +import { + ReviewProblemStepsContent, + mapStateToProps, +} from './ReviewProblemStepsContent'; + +jest.mock('data/redux', () => ({ + selectors: { + app: { + showRubric: (...args) => ({ showRubric: args }), + }, + requests: { + isCompleted: (...args) => ({ isCompleted: args }), + isFailed: (...args) => ({ isFailed: args }), + }, + }, +})); +jest.mock('containers/ResponseDisplay', () => 'ResponseDisplay'); +jest.mock('containers/Rubric', () => 'Rubric'); +jest.mock('containers/ReviewModal/ReviewErrors', () => 'ReviewErrors'); + +describe('ReviewContent component', () => { + describe('component', () => { + describe('render tests', () => { + test('snapshot: not loaded, no error', () => { + expect(shallow().isEmptyRender()).toEqual(true); + }); + test('snapshot: show rubric', () => { + expect(shallow()).toMatchSnapshot(); + }); + test('snapshot: hide rubric', () => { + expect(shallow()).toMatchSnapshot(); + }); + test('snapshot: failed, showRubric (errors only)', () => { + expect(shallow()).toMatchSnapshot(); + }); + }); + }); + describe('mapStateToProps', () => { + let mapped; + const testState = { some: 'test-state' }; + beforeEach(() => { + mapped = mapStateToProps(testState); + }); + const requestKey = RequestKeys.fetchSubmission; + test('showRubric loads from app.showRubric', () => { + expect(mapped.showRubric).toEqual(selectors.app.showRubric(testState)); + }); + test('isFailed loads from requests.isFailed(fetchSubmission)', () => { + expect(mapped.isFailed).toEqual(selectors.requests.isFailed(testState, { requestKey })); + }); + test('isLoadeed loads from requests.isCompleted(fetchSubmission)', () => { + expect(mapped.isLoaded).toEqual(selectors.requests.isCompleted(testState, { requestKey })); + }); + }); +}); diff --git a/src/containers/ReviewProblemStepsModal/ReviewProblemStepsModal.scss b/src/containers/ReviewProblemStepsModal/ReviewProblemStepsModal.scss new file mode 100644 index 00000000..6c9896ea --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/ReviewProblemStepsModal.scss @@ -0,0 +1,44 @@ +@import "@edx/paragon/scss/core/core"; + +.review-step-problems-modal-body { + background-color: $gray-300 !important; + padding: inherit; + background-color: #ececec !important; + + & > div.pgn__modal-body-content { + height: 100%; + + .row { + height: 100%; + } + } + + .content-block { + width: fit-content; + margin: auto; + height: 100%; + } + + .content-wrapper { + width: min-content; + } +} + +@include media-breakpoint-down(sm) { + .review-step-problems-modal-body { + padding: 0 !important; + overflow: hidden !important; + + & > div.pgn__modal-body-content { + overflow-y: scroll; + } + + .content-block .col { + padding: 0; + } + + .content-wrapper { + width: 100%; + } + } +} diff --git a/src/containers/ReviewProblemStepsModal/__snapshots__/ReviewProblemStepsContent.test.jsx.snap b/src/containers/ReviewProblemStepsModal/__snapshots__/ReviewProblemStepsContent.test.jsx.snap new file mode 100644 index 00000000..4a57d2bf --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/__snapshots__/ReviewProblemStepsContent.test.jsx.snap @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ReviewContent component component render tests snapshot: failed, showRubric (errors only) 1`] = ` +
+
+ +
+
+`; + +exports[`ReviewContent component component render tests snapshot: hide rubric 1`] = ` +
+
+ + + + + + + +
+
+`; + +exports[`ReviewContent component component render tests snapshot: show rubric 1`] = ` +
+
+ + + + + + +
+
+`; diff --git a/src/containers/ReviewProblemStepsModal/components/CloseReviewConfirmModal.jsx b/src/containers/ReviewProblemStepsModal/components/CloseReviewConfirmModal.jsx new file mode 100644 index 00000000..4e5cb069 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/CloseReviewConfirmModal.jsx @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; + +import ConfirmModal from 'components/ConfirmModal'; +import messages from './messages'; + +export const CloseReviewConfirmModal = ({ + intl, + isOpen, + onCancel, + onConfirm, +}) => ( + +); +CloseReviewConfirmModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onCancel: PropTypes.func.isRequired, + onConfirm: PropTypes.func.isRequired, + // injected + intl: intlShape.isRequired, +}; + +export default injectIntl(CloseReviewConfirmModal); diff --git a/src/containers/ReviewProblemStepsModal/components/CloseReviewConfirmModal.test.jsx b/src/containers/ReviewProblemStepsModal/components/CloseReviewConfirmModal.test.jsx new file mode 100644 index 00000000..85f89a3b --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/CloseReviewConfirmModal.test.jsx @@ -0,0 +1,24 @@ +import { shallow } from 'enzyme'; + +import { formatMessage } from 'testUtils'; +import { CloseReviewConfirmModal } from './CloseReviewConfirmModal'; + +jest.mock('components/ConfirmModal', () => 'ConfirmModal'); + +describe('CloseReviewConfirmModal', () => { + const props = { + intl: { formatMessage }, + isOpen: false, + onCancel: jest.fn().mockName('this.props.onCancel'), + onConfirm: jest.fn().mockName('this.props.onConfirm'), + }; + + describe('snapshot', () => { + test('closed', () => { + expect(shallow()).toMatchSnapshot(); + }); + test('open', () => { + expect(shallow()).toMatchSnapshot(); + }); + }); +}); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/ReviewProblemStepActions.scss b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/ReviewProblemStepActions.scss new file mode 100644 index 00000000..1fde9801 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/ReviewProblemStepActions.scss @@ -0,0 +1,37 @@ +@import "@edx/paragon/scss/core/core"; + +// action reviews +.review-actions { + padding: map_get($spacers, 3); + flex-direction: row; + background-color: $light-200; + + .review-actions-username { + flex-grow: 1; + } + .review-actions-status { + margin-left: map_get($spacers, 3); + vertical-align: middle; + } + .review-actions-group { + margin-left: 0; + flex-shrink: 0; + align-self: stretch; + + .submission-navigation { + float: right; + padding: map-get($spacers, 1); + } + } +} + +@include media-breakpoint-down(md) { + .review-actions { + flex-direction: column; + align-items: flex-start !important; + } + + .review-actions-username { + padding-bottom: map-get($spacers, 3); + } +} \ No newline at end of file diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.jsx new file mode 100644 index 00000000..3a024584 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.jsx @@ -0,0 +1,70 @@ +import React from 'react'; + +import { ActionRow } from '@edx/paragon'; + +import StatusBadge from 'components/StatusBadge'; +import SubmissionNavigation from 'containers/ReviewActions/components/SubmissionNavigation'; + +import './ReviewProblemStepActions.scss'; + +export const ReviewProblemStepActions = () => ( +
+ +
+

John Doe

+

jhon_20

+
+
+

Email

+

jhonvente@email.com

+
+
+

Submission ID

+

483234704918

+
+
+

Submission date

+

9/13/2023, 7:13:56 AM

+
+
+

Grade

+

3/10

+
+
+

Grading status

+

Upgraded

+
+ +
+

Problem steps

+

+ + + + +

+
+
+ +
+
+
+); + +export default ReviewProblemStepActions; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/messages.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/messages.js new file mode 100644 index 00000000..4d9a92e6 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/messages.js @@ -0,0 +1,21 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + pointsDisplay: { + id: 'ora-grading.ReviewActions.pointsDisplay', + defaultMessage: 'Score: {pointsEarned}/{pointsPossible}', + description: 'Review pane action bar score display', + }, + hideRubric: { + id: 'ora-grading.ReviewActions.hideRubric', + defaultMessage: 'Hide Rubric', + description: 'Review pane action bar Hide Rubric button text', + }, + showRubric: { + id: 'ora-grading.ReviewActions.showRubric', + defaultMessage: 'Show Rubric', + description: 'Review pane action bar Show Rubric button text', + }, +}); + +export default messages; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/ReviewProblemStepsContent.scss b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/ReviewProblemStepsContent.scss new file mode 100644 index 00000000..9588ee7a --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/ReviewProblemStepsContent.scss @@ -0,0 +1,6 @@ +.review-problem-steps-content { + padding: 32px; + .default-bold-color { + color: #00262B; + } +} \ No newline at end of file diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/AssessmentsTable.scss b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/AssessmentsTable.scss new file mode 100644 index 00000000..75c78e54 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/AssessmentsTable.scss @@ -0,0 +1,14 @@ +.assessmentButton:focus { + outline: none !important; + overflow: visible; + border-color: none !important; +} + +.pgn__data-table td { + color: #363636; +} + +.step-problems-button-badge { + margin-right: 8px; + padding: 0px !important; +} \ No newline at end of file diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/constants.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/constants.js new file mode 100644 index 00000000..77551df8 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/constants.js @@ -0,0 +1,91 @@ +export const receivedAssessmentData = [ + { + idAssessment: '55550453040', + reviewerName: 'Carlos Doe', + userName: 'carlos_doe_10', + email: 'carlos@email.com', + assessmentDate: '28-10-2023', + assessmentScores: [{ + id: 'aB3cD7eF', + type: 'Ideas', + quality: 'Fair', + rate: 1, + }, + { + id: '9GhI2jKl', + type: 'Content', + quality: 'Excellent', + rate: 5, + }], + problemStep: 'Staff', + feedback: 'This is working really well', + }, + { + idAssessment: '55550453042', + reviewerName: 'John Smith', + userName: 'john_smith_23', + email: 'john@email.com', + assessmentDate: '30-10-2023', + assessmentScores: [{ + id: 'M4nO8pQr', + type: 'Ideas', + quality: 'Good', + rate: 5, + }, + { + id: 'X5tU6vWx', + type: 'Content', + quality: 'Excellent', + rate: 5, + }], + problemStep: 'Training', + feedback: 'Great progress!', + }, + { + idAssessment: '55550453048', + reviewerName: 'Emily Johnson', + userName: 'emily_j', + email: 'emily@email.com', + assessmentDate: '29-10-2023', + assessmentScores: [{ + id: 'PqW0uVwX', + type: 'Ideas', + quality: null, + rate: null, + }, + { + id: 'ZabC12DE', + type: 'Content', + quality: null, + rate: null, + }], + problemStep: 'Peers', + feedback: 'Needs improvement in certain areas.', + }, + { + idAssessment: '55550453050', + reviewerName: 'Sarah Brown', + userName: 'sarah_brown_45', + email: 'sarah@email.com', + assessmentDate: '31-10-2023', + assessmentScores: [{ + id: 'Yz123456', + type: 'Ideas', + quality: 'Good', + rate: 3, + }, + { + id: 'L7iR9oSt', + type: 'Content', + quality: 'Excellent', + rate: 5, + }], + problemStep: 'Self', + feedback: 'Excellent work, keep it up!', + }, +]; + +export const givenAssessmentData = receivedAssessmentData.map(assessment => ({ + ...assessment, + learnerName: assessment.reviewerName, +})); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx new file mode 100644 index 00000000..7eeb493b --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx @@ -0,0 +1,117 @@ +import { useState } from 'react'; +import { + Row, Button, + DataTable, + Hyperlink, +} from '@edx/paragon'; + +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; + +import StatusBadge from 'components/StatusBadge'; +import messages from './messages'; +import './AssessmentsTable.scss'; + +import { receivedAssessmentData, givenAssessmentData } from './constants'; + +const AssessmentsTable = ({ intl }) => { + const [assessmentSelected, setAssessmentSelected] = useState('receivedAssessmentSelected'); + const handleReceivedAssessments = () => { + setAssessmentSelected('receivedAssessmentSelected'); + }; + const handleGivenAssessments = () => { + setAssessmentSelected('givenAssessmentSelected'); + }; + + const assessmentScoreColumn = ({ value: assessmentScores }) => ( +
    + {assessmentScores.map(({ + id, type, quality, rate, + }) =>
  • {type}: {quality && rate ? `${quality} (${rate})` : '-'}
  • )} +
+ ); + + const assessmentEmailColumn = ({ value: email }) => ( + + {email} + + ); + + const assessmentProblemStepBadge = ({ value }) => ( + + ); + + const isReceivedAssessmentSelected = assessmentSelected === 'receivedAssessmentSelected'; + + return ( +
+

{intl.formatMessage(messages.assessmentsTableTitle)}

+ + + + + + + + + + +
+ ); +}; + +AssessmentsTable.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(AssessmentsTable); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx new file mode 100644 index 00000000..155ad82e --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import AssessmentsTable from '.'; + +describe('AssessmentsTable component', () => { + it('renders without crashing', () => { + const wrapper = shallow(); + expect(wrapper.exists()).toBe(true); + }); +}); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/messages.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/messages.js new file mode 100644 index 00000000..2f238d95 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/messages.js @@ -0,0 +1,21 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + assessmentsTableTitle: { + id: 'ora-grading.ReviewProblemStepsContent.assessmentsTableTitle', + defaultMessage: 'Assessments progress', + description: 'ORA Grading override confirm modal title', + }, + assessmentsReceivedButtonTitle: { + id: 'ora-grading.ReviewProblemStepsContent.assessmentsReceivedButtonTitle', + defaultMessage: 'Assessments received', + description: 'ORA Grading override confirm modal warning/content text', + }, + assessmentsGivenButtonTitle: { + id: 'ora-grading.ReviewProblemStepsContent.assessmentsGivenButtonTitle', + defaultMessage: 'Assessments given', + description: 'ORA Grading override confirm modal confirm button', + }, +}); + +export default messages; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/OverrideGradeConfirmModal.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/OverrideGradeConfirmModal.jsx new file mode 100644 index 00000000..27cbd527 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/OverrideGradeConfirmModal.jsx @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; + +import ConfirmModal from 'components/ConfirmModal'; +import messages from './messages'; + +export const OverrideGradeConfirmModal = ({ + intl, + isOpen, + onCancel, + onConfirm, +}) => ( + +); +OverrideGradeConfirmModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onCancel: PropTypes.func.isRequired, + onConfirm: PropTypes.func.isRequired, + // injected + intl: intlShape.isRequired, +}; + +export default injectIntl(OverrideGradeConfirmModal); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/OverrideGradeConfirmModal.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/OverrideGradeConfirmModal.test.jsx new file mode 100644 index 00000000..26d6378a --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/OverrideGradeConfirmModal.test.jsx @@ -0,0 +1,21 @@ +import { shallow } from 'enzyme'; + +import { formatMessage } from 'testUtils'; +import { OverrideGradeConfirmModal } from './OverrideGradeConfirmModal'; + +jest.mock('components/ConfirmModal', () => 'ConfirmModal'); + +describe('OverrideGradeConfirmModal', () => { + const props = { + intl: { formatMessage }, + isOpen: false, + onCancel: jest.fn().mockName('this.props.onCancel'), + onConfirm: jest.fn().mockName('this.props.onConfirm'), + }; + test('snapshot: closed', () => { + expect(shallow()).toMatchSnapshot(); + }); + test('snapshot: open', () => { + expect(shallow()).toMatchSnapshot(); + }); +}); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/ResponseList.scss b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/ResponseList.scss new file mode 100644 index 00000000..a0a0af04 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/ResponseList.scss @@ -0,0 +1,9 @@ +.list { + color: #454545; + &__disabled { + color: #454545; + background-color: transparent; + border-color: transparent; + opacity: 0.4; + } +} \ No newline at end of file diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.jsx new file mode 100644 index 00000000..d5688c69 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.jsx @@ -0,0 +1,31 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { + Collapsible, + Icon, +} from '@edx/paragon'; +import { + KeyboardArrowDown, KeyboardArrowUp, +} from '@edx/paragon/icons'; + +export const ResponseItem = ({ title, response }) => ( + + +

{title}

+ + +
+ + + {response} + +
+); + +ResponseItem.propTypes = { + title: PropTypes.string.isRequired, + response: PropTypes.string.isRequired, +}; + +export default ResponseItem; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.jsx new file mode 100644 index 00000000..8a6c6fd5 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.jsx @@ -0,0 +1,93 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +import { + Col, Row, Button, +} from '@edx/paragon'; +import { + BsMicrosoft, FormatListBulleted, +} from '@edx/paragon/icons'; +import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; + +import { actions, selectors } from 'data/redux'; +import { RequestKeys } from 'data/constants/requests'; +import Rubric from 'containers/Rubric'; + +import StartGradingButton from '../StartGradingButton'; +import ResponseItem from './components/ResponseItem'; +import messages from './messages'; +import './ResponseList.scss'; + +const responsesList = [ + { + id: 'adb123', + title: 'Prompt 1', + response: + ' Demanding, but definitely doable. Social, but educational. A focused topic, but applicable skills. CS50 is the quintessential Harvard (and Yale!) course', + }, + { + id: 'dfg456', + title: 'Prompt 2', + response: + ' Demanding, but definitely doable. Social, but educational. A focused topic, but applicable skills. CS50 is the quintessential Harvard (and Yale!) course', + }, +]; + +const ResponsesList = ({ + intl, showRubric, isLoaded, toggleShowRubric, +}) => { + const [isFormatList, setIsFormatList] = useState(true); + const toggleFormatList = (formatList) => setIsFormatList(formatList); + const formatListClass = isFormatList ? 'list__active' : 'list__disabled'; + const formatGridClass = !isFormatList ? 'list__active' : 'list__disabled'; + return ( + <> + + +

{intl.formatMessage(messages.responsesDetailListTitle)}

+ + + + + {isLoaded && ( + <> + + + + )} + +
+ + {showRubric && } + + +
+ {responsesList.map(({ id, ...responseData }) => )} +
+ + ); +}; + +ResponsesList.defaultProps = { + isLoaded: false, +}; +ResponsesList.propTypes = { + showRubric: PropTypes.bool.isRequired, + toggleShowRubric: PropTypes.func.isRequired, + isLoaded: PropTypes.bool, + intl: intlShape.isRequired, +}; + +export const mapStateToProps = (state) => ({ + showRubric: selectors.app.showRubric(state), + isLoaded: selectors.requests.isCompleted(state, { requestKey: RequestKeys.fetchSubmission }), +}); + +export const mapDispatchToProps = { + toggleShowRubric: actions.app.toggleShowRubric, +}; + +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ResponsesList)); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx new file mode 100644 index 00000000..9c881548 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import ResponsesList from '.'; + +describe('ResponsesList component', () => { + it('renders without crashing', () => { + const wrapper = shallow(); + expect(wrapper.exists()).toBe(true); + }); +}); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/messages.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/messages.js new file mode 100644 index 00000000..5e1c1a69 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/messages.js @@ -0,0 +1,21 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + responsesDetailListTitle: { + id: 'ora-grading.ReviewProblemStepsContent.responsesDetailListTitle', + defaultMessage: 'Responses', + description: 'ORA title for response list', + }, + hideRubric: { + id: 'ora-grading.ReviewProblemStepsContent.hideRubric', + defaultMessage: 'Hide Rubric', + description: 'Review pane action bar Hide Rubric button text', + }, + showRubric: { + id: 'ora-grading.ReviewProblemStepsContent.showRubric', + defaultMessage: 'Show Rubric', + description: 'Review pane action bar Show Rubric button text', + }, +}); + +export default messages; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/__snapshots__/index.test.jsx.snap b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/__snapshots__/index.test.jsx.snap new file mode 100644 index 00000000..6e87c818 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/__snapshots__/index.test.jsx.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`StartGradingButton component component snapshots hide: renders empty component if hook.hide is true 1`] = `""`; + +exports[`StartGradingButton component component snapshots smoke test: forwards props to components from hooks 1`] = ` + + From 3877fabbc2c24e865324e071a29a688523304ca0 Mon Sep 17 00:00:00 2001 From: Jhon Vente Date: Mon, 9 Oct 2023 10:42:43 -0500 Subject: [PATCH 03/24] refactor: format code --- .../ReviewProblemStepActions.scss | 2 +- .../ReviewProblemStepsContent.scss | 2 +- .../AssessmentsTable/AssessmentsTable.scss | 16 ++++++++-------- .../components/ResponsesList/ResponseList.scss | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/ReviewProblemStepActions.scss b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/ReviewProblemStepActions.scss index 1fde9801..54793ce5 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/ReviewProblemStepActions.scss +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/ReviewProblemStepActions.scss @@ -34,4 +34,4 @@ .review-actions-username { padding-bottom: map-get($spacers, 3); } -} \ No newline at end of file +} diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/ReviewProblemStepsContent.scss b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/ReviewProblemStepsContent.scss index 9588ee7a..684342c8 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/ReviewProblemStepsContent.scss +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/ReviewProblemStepsContent.scss @@ -3,4 +3,4 @@ .default-bold-color { color: #00262B; } -} \ No newline at end of file +} diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/AssessmentsTable.scss b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/AssessmentsTable.scss index 75c78e54..96634960 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/AssessmentsTable.scss +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/AssessmentsTable.scss @@ -1,14 +1,14 @@ -.assessmentButton:focus { - outline: none !important; - overflow: visible; - border-color: none !important; +.assessmentButton:focus { + outline: none !important; + overflow: visible; + border-color: none !important; } .pgn__data-table td { - color: #363636; + color: #363636; } .step-problems-button-badge { - margin-right: 8px; - padding: 0px !important; -} \ No newline at end of file + margin-right: 8px; + padding: 0px !important; +} diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/ResponseList.scss b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/ResponseList.scss index a0a0af04..0725a3b1 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/ResponseList.scss +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/ResponseList.scss @@ -6,4 +6,4 @@ border-color: transparent; opacity: 0.4; } -} \ No newline at end of file +} From 323c42f9726156a3a21d03bfecf0be9dcfd6792f Mon Sep 17 00:00:00 2001 From: Jhon Vente Date: Mon, 9 Oct 2023 10:50:10 -0500 Subject: [PATCH 04/24] docs: update submissionuuid param type --- src/data/redux/thunkActions/grading.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/redux/thunkActions/grading.js b/src/data/redux/thunkActions/grading.js index 56b69bd7..5481bc30 100644 --- a/src/data/redux/thunkActions/grading.js +++ b/src/data/redux/thunkActions/grading.js @@ -34,7 +34,7 @@ export const loadPrev = () => (dispatch) => { * Then loads current selection and prefetches neighbors. * @param {string[]} submissionUUIDs - ordered list of submissionUUIDs for selected submissions * @param {boolean} showReview - show modal for the review - * @param {boolean} submissionUUIDParam - to set an expecifict submission ID to load + * @param {string} submissionUUIDParam - to set an expecifict submission ID to load */ export const loadSelectionForReview = ( submissionUUIDs, From 7bb2d6053749a50aad3c1b08e781d55870b62c64 Mon Sep 17 00:00:00 2001 From: Jhon Vente Date: Mon, 9 Oct 2023 15:22:38 -0500 Subject: [PATCH 05/24] fix: adding more test cases --- src/containers/ListView/SubmissionsTable.jsx | 5 -- .../ListView/SubmissionsTable.test.jsx | 72 +++++++++++++++++++ .../ReviewProblemStepActions/index.test.jsx | 33 +++++++++ .../components/ResponseItem/index.test.jsx | 31 ++++++++ src/data/redux/problem-steps/reducer.test.js | 25 +++++++ src/data/redux/problem-steps/selector.test.js | 18 +++++ 6 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.test.jsx create mode 100644 src/data/redux/problem-steps/reducer.test.js create mode 100644 src/data/redux/problem-steps/selector.test.js diff --git a/src/containers/ListView/SubmissionsTable.jsx b/src/containers/ListView/SubmissionsTable.jsx index 6fadf28e..a36da638 100644 --- a/src/containers/ListView/SubmissionsTable.jsx +++ b/src/containers/ListView/SubmissionsTable.jsx @@ -68,10 +68,6 @@ export class SubmissionsTable extends React.Component { formatStatus = ({ value }) => ; - handleProblemStepClick = (problemStepType) => { - console.log(problemStepType); - }; - formatProblemStepsStatus = () => { const stepProblems = Object.keys(problemSteps); return ( @@ -80,7 +76,6 @@ export class SubmissionsTable extends React.Component { - + + { + let el; + + beforeEach(() => { + el = shallow(); + }); it('renders without crashing', () => { - const wrapper = shallow(); - expect(wrapper.exists()).toBe(true); + expect(el.exists()).toBe(true); + }); + + it('renders the correct title', () => { + const h3Title = el.find('h3'); + expect(h3Title.text()).toBe( + formatMessage(messages.assessmentsTableTitle), + ); + }); + + it('should render assessment given button and assessment received button', () => { + const assessmentReceivedButton = el.find('[data-testid="assessments-received-button"]'); + const assessmentGivenButton = el.find('[data-testid="assessments-given-button"]'); + + expect(assessmentReceivedButton.exists()).toBe(true); + expect(assessmentGivenButton.exists()).toBe(true); + + expect(assessmentReceivedButton.text()).toBe( + formatMessage(messages.assessmentsReceivedButtonTitle), + ); + expect(assessmentGivenButton.text()).toBe( + formatMessage(messages.assessmentsGivenButtonTitle), + ); + }); + + describe('DataTable', () => { + let table; + let tableProps; + beforeEach(() => { + table = el.find(DataTable); + tableProps = table.props(); + }); + test.each([ + 'isSelectable', + ])('%s', key => expect(tableProps[key]).toEqual(true)); + + describe('individual columns', () => { + let columns; + beforeEach(() => { + columns = tableProps.columns; + }); + + it('ID Assessment column', () => { + expect(columns[0]).toEqual({ + Header: formatMessage(messages.idAssessmentColumnTitle), + accessor: 'idAssessment', + }); + }); + it('Reviewer name or Learner name column', () => { + expect(columns[1]).toEqual({ + Header: formatMessage(messages.reviewerNameColumnTitle), + accessor: 'reviewerName', + }); + }); + it('User name column', () => { + expect(columns[2]).toEqual({ + Header: formatMessage(messages.usernameColumnTitle), + accessor: 'userName', + }); + }); + it('Email column', () => { + const emailColumn = tableProps.columns.find((column) => column.accessor === 'email'); + expect(emailColumn.Header).toBe( + formatMessage(messages.emailColumnTitle), + ); + }); + it('Assessment date column', () => { + const assessmentDateColumn = tableProps.columns.find((column) => column.accessor === 'assessmentDate'); + expect(assessmentDateColumn.Header).toBe( + formatMessage(messages.assessmentDateColumnTitle), + ); + }); + + it('Assessment scores column', () => { + const assessmentScoresColumn = tableProps.columns.find((column) => column.accessor === 'assessmentScores'); + expect(assessmentScoresColumn.Header).toBe( + formatMessage(messages.assessmentScoresColumnTitle), + ); + }); + + it('Problem step column', () => { + const problemStepColumn = tableProps.columns.find((column) => column.accessor === 'problemStep'); + expect(problemStepColumn.Header).toBe( + formatMessage(messages.problemStepColumnTitle), + ); + }); + + it('Feedback column', () => { + const feedbackColumn = tableProps.columns.find((column) => column.accessor === 'feedback'); + expect(feedbackColumn.Header).toBe( + formatMessage(messages.feedbackColumnTitle), + ); + }); + }); }); }); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/messages.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/messages.js index 2f238d95..0a98d44e 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/messages.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/messages.js @@ -16,6 +16,51 @@ const messages = defineMessages({ defaultMessage: 'Assessments given', description: 'ORA Grading override confirm modal confirm button', }, + idAssessmentColumnTitle: { + id: 'ora-grading.ReviewProblemStepsContent.idAssessmentColumnTitle', + defaultMessage: 'ID Assessment', + description: 'ORA Grading ID Assessment column title', + }, + reviewerNameColumnTitle: { + id: 'ora-grading.ReviewProblemStepsContent.reviewerNameColumnTitle', + defaultMessage: 'Reviewer name', + description: 'ORA Grading Reviewer Name column title', + }, + learnerNameColumnTitle: { + id: 'ora-grading.ReviewProblemStepsContent.learnerNameColumnTitle', + defaultMessage: 'Learner name', + description: 'ORA Grading Reviewer Name column title', + }, + usernameColumnTitle: { + id: 'ora-grading.ReviewProblemStepsContent.usernameColumnTitle', + defaultMessage: 'Username', + description: 'ORA Grading Username column title', + }, + emailColumnTitle: { + id: 'ora-grading.ReviewProblemStepsContent.emailColumnTitle', + defaultMessage: 'Email', + description: 'ORA Grading Email column title', + }, + assessmentDateColumnTitle: { + id: 'ora-grading.ReviewProblemStepsContent.assessmentDateColumnTitle', + defaultMessage: 'Assessment date', + description: 'ORA Grading Assessment Date column title', + }, + assessmentScoresColumnTitle: { + id: 'ora-grading.ReviewProblemStepsContent.assessmentScoresColumnTitle', + defaultMessage: 'Assessment scores', + description: 'ORA Grading Assessment Scores column title', + }, + problemStepColumnTitle: { + id: 'ora-grading.ReviewProblemStepsContent.problemStepColumnTitle', + defaultMessage: 'Problem step', + description: 'ORA Grading Problem Step column title', + }, + feedbackColumnTitle: { + id: 'ora-grading.ReviewProblemStepsContent.feedbackColumnTitle', + defaultMessage: 'Feedback', + description: 'ORA Grading Feedback column title', + }, }); export default messages; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.jsx index 8a6c6fd5..49c17fcd 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.jsx @@ -19,23 +19,8 @@ import ResponseItem from './components/ResponseItem'; import messages from './messages'; import './ResponseList.scss'; -const responsesList = [ - { - id: 'adb123', - title: 'Prompt 1', - response: - ' Demanding, but definitely doable. Social, but educational. A focused topic, but applicable skills. CS50 is the quintessential Harvard (and Yale!) course', - }, - { - id: 'dfg456', - title: 'Prompt 2', - response: - ' Demanding, but definitely doable. Social, but educational. A focused topic, but applicable skills. CS50 is the quintessential Harvard (and Yale!) course', - }, -]; - -const ResponsesList = ({ - intl, showRubric, isLoaded, toggleShowRubric, +export const ResponsesList = ({ + intl, showRubric, isLoaded, toggleShowRubric, responsesList, }) => { const [isFormatList, setIsFormatList] = useState(true); const toggleFormatList = (formatList) => setIsFormatList(formatList); @@ -48,11 +33,11 @@ const ResponsesList = ({

{intl.formatMessage(messages.responsesDetailListTitle)}

- - + + {isLoaded && ( <> - @@ -73,11 +58,19 @@ const ResponsesList = ({ ResponsesList.defaultProps = { isLoaded: false, + responsesList: [], }; ResponsesList.propTypes = { showRubric: PropTypes.bool.isRequired, toggleShowRubric: PropTypes.func.isRequired, isLoaded: PropTypes.bool, + responsesList: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + response: PropTypes.string.isRequired, + }), + ), intl: intlShape.isRequired, }; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx index 9c881548..bbf276ea 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx @@ -1,10 +1,70 @@ import React from 'react'; import { shallow } from 'enzyme'; -import ResponsesList from '.'; +import { formatMessage } from 'testUtils'; +import messages from './messages'; +import { ResponsesList } from '.'; describe('ResponsesList component', () => { + const mockToggleShowRubric = jest.fn(); + + const defaultProps = { + showRubric: false, + toggleShowRubric: mockToggleShowRubric, + isLoaded: true, + }; + + let el; + + beforeEach(() => { + el = shallow(); + }); + it('renders without crashing', () => { - const wrapper = shallow(); - expect(wrapper.exists()).toBe(true); + expect(el.exists()).toBe(true); + }); + + it('renders the correct title', () => { + const h3Title = el.find('h3'); + expect(h3Title.text()).toBe( + formatMessage(messages.responsesDetailListTitle), + ); + }); + + it('list ordered button and list-grid-button must be rendered', () => { + const formatListOrderedButton = el.find( + '[data-testid="list-ordered-button"]', + ); + expect(formatListOrderedButton.exists()).toBe(true); + const formatListGridButton = el.find('[data-testid="list-grid-button"]'); + expect(formatListGridButton.exists()).toBe(true); + }); + + it('When has isLoaded prop should show "Show Rubric" button" ', () => { + const showRubricButton = el.find('[data-testid="show-rubric-button"]'); + expect(showRubricButton.exists()).toBe(true); + }); + + it('renders the list of responses', () => { + const responsesList = [ + { + id: 'adb123', + title: 'Prompt 1', + response: + ' Demanding, but definitely doable. Social, but educational. A focused topic, but applicable skills. CS50 is the quintessential Harvard (and Yale!) course', + }, + { + id: 'dfg456', + title: 'Prompt 2', + response: + ' Demanding, but definitely doable. Social, but educational. A focused topic, but applicable skills. CS50 is the quintessential Harvard (and Yale!) course', + }, + ]; + + const wrapper = shallow(); + + responsesList.forEach((response) => { + const responseItem = wrapper.find(`ResponseItem[title="${response.title}"][response="${response.response}"]`); + expect(responseItem.exists()).toBe(true); + }); }); }); diff --git a/src/containers/Rubric/index.jsx b/src/containers/Rubric/index.jsx index af9314b7..a18e501f 100644 --- a/src/containers/Rubric/index.jsx +++ b/src/containers/Rubric/index.jsx @@ -28,7 +28,7 @@ export const Rubric = ({ intl }) => { } = hooks.rendererHooks({ dispatch }); return ( <> - +

{intl.formatMessage(messages.rubric)}


From 3d39f41f5de05a0064facc059ed2b19289bae217 Mon Sep 17 00:00:00 2001 From: johnvente Date: Thu, 9 Nov 2023 15:42:33 -0500 Subject: [PATCH 08/24] fix: removing testid for rubric component --- src/containers/Rubric/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/Rubric/index.jsx b/src/containers/Rubric/index.jsx index a18e501f..af9314b7 100644 --- a/src/containers/Rubric/index.jsx +++ b/src/containers/Rubric/index.jsx @@ -28,7 +28,7 @@ export const Rubric = ({ intl }) => { } = hooks.rendererHooks({ dispatch }); return ( <> - +

{intl.formatMessage(messages.rubric)}


From 7fe98074cbca9de59355b737a5d34d8b7e3776b4 Mon Sep 17 00:00:00 2001 From: johnvente Date: Fri, 10 Nov 2023 11:01:33 -0500 Subject: [PATCH 09/24] fix: unit testing for not covered test cases --- .../ListView/SubmissionsTable.test.jsx | 52 +++++ .../ReviewProblemStepActions/messages.js | 3 +- .../components/AssessmentsTable/index.jsx | 48 +++-- .../AssessmentsTable/index.test.jsx | 196 +++++++++++++++++- 4 files changed, 284 insertions(+), 15 deletions(-) diff --git a/src/containers/ListView/SubmissionsTable.test.jsx b/src/containers/ListView/SubmissionsTable.test.jsx index 6843bf1b..842f1a51 100644 --- a/src/containers/ListView/SubmissionsTable.test.jsx +++ b/src/containers/ListView/SubmissionsTable.test.jsx @@ -325,6 +325,58 @@ describe('SubmissionsTable component', () => { expect(props.setActiveSubmissionIndex).toHaveBeenCalledTimes(1); expect(props.setProblemStepsModal).toHaveBeenCalledTimes(1); }); + it('should call the appropriate when button view details is called', () => { + const mockData = [ + { + gradingStatus: 'ungraded', + submissionUUID: '701616b5-b394-47e0-bd2d-cd13462b9471', + username: 'username1', + teamName: null, + dateSubmitted: '2023-10-04 17:13:22.873876+00:00', + dateGraded: 'None', + gradedBy: null, + score: null, + }, + { + gradingStatus: 'ungraded', + submissionUUID: '29c3c216-56e0-4686-a925-8fe65641eb8e', + username: 'username2', + teamName: null, + dateSubmitted: '2023-10-05 15:45:18.732687+00:00', + dateGraded: 'None', + gradedBy: null, + score: null, + }, + ]; + + const mockCurrentRow = { + id: '0', + index: 0, + isSelected: false, + isSomeSelected: false, + original: { + dateGraded: 'None', + dateSubmitted: '2023-10-04 17:13:22.873876+00:00', + gradedBy: null, + gradingStatus: 'ungraded', + score: null, + submissionUUID: '701616b5-b394-47e0-bd2d-cd13462b9471', + teamName: null, + username: 'username1', + }, + + }; + + el.instance().handleProblemStepsDetailClick = jest.fn().mockName('this.handleProblemStepsDetailClick'); + + const wrapper = shallow(el.instance().problemStepsViewDetails({ data: mockData, row: mockCurrentRow })); + const viewDetailsButton = wrapper.find('[data-testid="button-view-details"]'); + expect(viewDetailsButton.exists()).toBe(true); + + viewDetailsButton.simulate('click'); + expect(el.instance().handleProblemStepsDetailClick).toHaveBeenCalled(); + expect(el.instance().handleProblemStepsDetailClick).toHaveBeenCalledWith(mockData, mockCurrentRow); + }); }); }); }); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/messages.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/messages.js index 4d9a92e6..d9bd8d90 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/messages.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +/* import { defineMessages } from '@edx/frontend-platform/i18n'; const messages = defineMessages({ pointsDisplay: { @@ -19,3 +19,4 @@ const messages = defineMessages({ }); export default messages; + */ diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx index 2c010583..7b4eb3b4 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx @@ -4,22 +4,25 @@ import { } from '@edx/paragon'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import PropTypes from 'prop-types'; import StatusBadge from 'components/StatusBadge'; import messages from './messages'; import './AssessmentsTable.scss'; -import { receivedAssessmentData, givenAssessmentData } from './constants'; - -export const AssessmentsTable = ({ intl }) => { +export const AssessmentsTable = ({ + intl, assessmentsList, onClickReceivedAssessment, onClickGivenAssessment, +}) => { const [assessmentSelected, setAssessmentSelected] = useState( 'receivedAssessmentSelected', ); const handleReceivedAssessments = () => { setAssessmentSelected('receivedAssessmentSelected'); + onClickReceivedAssessment(); }; const handleGivenAssessments = () => { setAssessmentSelected('givenAssessmentSelected'); + onClickGivenAssessment(); }; const assessmentScoreColumn = ({ value: assessmentScores }) => ( @@ -83,16 +86,8 @@ export const AssessmentsTable = ({ intl }) => { { ); }; +AssessmentsTable.defaultProps = { + assessmentsList: [], + onClickReceivedAssessment: () => {}, + onClickGivenAssessment: () => {}, +}; + AssessmentsTable.propTypes = { intl: intlShape.isRequired, + onClickReceivedAssessment: PropTypes.func, + onClickGivenAssessment: PropTypes.func, + assessmentsList: PropTypes.arrayOf( + PropTypes.shape({ + idAssessment: PropTypes.string.isRequired, + reviewerName: PropTypes.string.isRequired, + userName: PropTypes.string.isRequired, + email: PropTypes.string.isRequired, + assessmentDate: PropTypes.string.isRequired, + assessmentScores: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + quality: PropTypes.string, + rate: PropTypes.number, + }), + ).isRequired, + problemStep: PropTypes.string.isRequired, + feedback: PropTypes.string.isRequired, + }), + ), }; export default injectIntl(AssessmentsTable); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx index 49556c3b..304a6da0 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx @@ -3,6 +3,8 @@ import { shallow } from 'enzyme'; import { formatMessage } from 'testUtils'; import { DataTable, + Hyperlink, + Button, } from '@edx/paragon'; import messages from './messages'; @@ -10,9 +12,105 @@ import { AssessmentsTable } from '.'; describe('AssessmentsTable component', () => { let el; + let handleReceivedAssessmentsSpy; + let handleGivenAssessmentsSpy; + const mockDataTableData = [ + { + idAssessment: '55550453040', + reviewerName: 'Carlos Doe', + userName: 'carlos_doe_10', + email: 'carlos@email.com', + assessmentDate: '28-10-2023', + assessmentScores: [{ + id: 'aB3cD7eF', + type: 'Ideas', + quality: 'Fair', + rate: 1, + }, + { + id: '9GhI2jKl', + type: 'Content', + quality: 'Excellent', + rate: 5, + }], + problemStep: 'Staff', + feedback: 'This is working really well', + }, + { + idAssessment: '55550453042', + reviewerName: 'John Smith', + userName: 'john_smith_23', + email: 'john@email.com', + assessmentDate: '30-10-2023', + assessmentScores: [{ + id: 'M4nO8pQr', + type: 'Ideas', + quality: 'Good', + rate: 5, + }, + { + id: 'X5tU6vWx', + type: 'Content', + quality: 'Excellent', + rate: 5, + }], + problemStep: 'Training', + feedback: 'Great progress!', + }, + { + idAssessment: '55550453048', + reviewerName: 'Emily Johnson', + userName: 'emily_j', + email: 'emily@email.com', + assessmentDate: '29-10-2023', + assessmentScores: [{ + id: 'PqW0uVwX', + type: 'Ideas', + quality: null, + rate: null, + }, + { + id: 'ZabC12DE', + type: 'Content', + quality: null, + rate: null, + }], + problemStep: 'Peers', + feedback: 'Needs improvement in certain areas.', + }, + { + idAssessment: '55550453050', + reviewerName: 'Sarah Brown', + userName: 'sarah_brown_45', + email: 'sarah@email.com', + assessmentDate: '31-10-2023', + assessmentScores: [{ + id: 'Yz123456', + type: 'Ideas', + quality: 'Good', + rate: 3, + }, + { + id: 'L7iR9oSt', + type: 'Content', + quality: 'Excellent', + rate: 5, + }], + problemStep: 'Self', + feedback: 'Excellent work, keep it up!', + }, + ]; beforeEach(() => { - el = shallow(); + handleReceivedAssessmentsSpy = jest.fn(); + handleGivenAssessmentsSpy = jest.fn(); + + el = shallow(); }); it('renders without crashing', () => { expect(el.exists()).toBe(true); @@ -40,6 +138,32 @@ describe('AssessmentsTable component', () => { ); }); + it('should call the correct onClick handler when the "Received Assessments" button is clicked', () => { + const assessmentReceivedButton = el.find('[data-testid="assessments-received-button"]'); + assessmentReceivedButton.simulate('click'); + + el.setProps({}); + el.update(); + + const assessmentReceivedButtonAfterClick = el.find('[data-testid="assessments-received-button"]'); + expect(assessmentReceivedButtonAfterClick.prop('variant')).toBe('primary'); + expect(handleReceivedAssessmentsSpy).toHaveBeenCalled(); + }); + + it('should call the correct onClick handler when the "Given Assessments" button is clicked', () => { + const assessmentGivenButton = el.find('[data-testid="assessments-given-button"]'); + + assessmentGivenButton.simulate('click'); + + el.setProps({}); + el.update(); + + const assessmentGivenButtonAfterClick = el.find('[data-testid="assessments-given-button"]'); + expect(assessmentGivenButton.prop('variant')).toBe('inverse-primary'); + expect(assessmentGivenButtonAfterClick.prop('variant')).toBe('primary'); + expect(handleGivenAssessmentsSpy).toHaveBeenCalled(); + }); + describe('DataTable', () => { let table; let tableProps; @@ -90,6 +214,13 @@ describe('AssessmentsTable component', () => { it('Assessment scores column', () => { const assessmentScoresColumn = tableProps.columns.find((column) => column.accessor === 'assessmentScores'); + const [firstItemMock] = mockDataTableData; + const { assessmentScores } = firstItemMock; + const Cell = () => assessmentScoresColumn.Cell({ value: assessmentScores }); + const wrapper = shallow(); + console.log('assessmentScoresColumn', assessmentScoresColumn); + console.log('tableProps', tableProps); + console.log(wrapper.debug()); expect(assessmentScoresColumn.Header).toBe( formatMessage(messages.assessmentScoresColumnTitle), ); @@ -109,5 +240,68 @@ describe('AssessmentsTable component', () => { ); }); }); + describe('behavior columns', () => { + describe('Assessment Score Column', () => { + let assessmentScoresColumn; + let firstItemMock; + let Cell; + beforeEach(() => { + assessmentScoresColumn = tableProps.columns.find((column) => column.accessor === 'assessmentScores'); + const [firstItemMockData] = mockDataTableData; + firstItemMock = firstItemMockData; + const { assessmentScores } = firstItemMock; + Cell = () => assessmentScoresColumn.Cell({ value: assessmentScores }); + }); + + it('renders assessment scores correctly', () => { + const wrapper = shallow(); + expect(wrapper.find('li')).toHaveLength(2); + }); + + it('renders assessment scores with correct values', () => { + const wrapper = shallow(); + expect(wrapper.find('li').at(0).text()).toBe('Ideas: Fair (1)'); + expect(wrapper.find('li').at(1).text()).toBe('Content: Excellent (5)'); + }); + }); + describe('Assessment Email Column', () => { + let emailColumn; + let firstItemMock; + let Cell; + let emailValue; + beforeEach(() => { + emailColumn = tableProps.columns.find((column) => column.accessor === 'email'); + const [firstItemMockData] = mockDataTableData; + firstItemMock = firstItemMockData; + const { email } = firstItemMock; + emailValue = email; + Cell = () => emailColumn.Cell({ value: email }); + }); + + it('renders Hyperlink with correct email', () => { + const wrapper = shallow(); + expect(wrapper.find(Hyperlink).prop('children')).toBe(emailValue); + }); + }); + + describe('Assessment Problem Step Badge', () => { + let problemStepColumn; + let firstItemMock; + let Cell; + beforeEach(() => { + problemStepColumn = tableProps.columns.find((column) => column.accessor === 'problemStep'); + const [firstItemMockData] = mockDataTableData; + firstItemMock = firstItemMockData; + const { problemStep } = firstItemMock; + Cell = () => problemStepColumn.Cell({ value: problemStep }); + }); + + it('renders Button with correct status and title', () => { + const wrapper = shallow(); + expect(wrapper.find(Button).prop('children').props.status).toBe('graded'); + expect(wrapper.find(Button).prop('children').props.title).toBe('Staff'); + }); + }); + }); }); }); From a4fdbaaa45887f582f87f1d221527e0181bbcb21 Mon Sep 17 00:00:00 2001 From: johnvente Date: Fri, 10 Nov 2023 11:03:05 -0500 Subject: [PATCH 10/24] fix: remove unnecesary console --- .../components/AssessmentsTable/index.test.jsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx index 304a6da0..24252f30 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx @@ -214,13 +214,6 @@ describe('AssessmentsTable component', () => { it('Assessment scores column', () => { const assessmentScoresColumn = tableProps.columns.find((column) => column.accessor === 'assessmentScores'); - const [firstItemMock] = mockDataTableData; - const { assessmentScores } = firstItemMock; - const Cell = () => assessmentScoresColumn.Cell({ value: assessmentScores }); - const wrapper = shallow(); - console.log('assessmentScoresColumn', assessmentScoresColumn); - console.log('tableProps', tableProps); - console.log(wrapper.debug()); expect(assessmentScoresColumn.Header).toBe( formatMessage(messages.assessmentScoresColumnTitle), ); From 2fe9c3009431c2bb44d19c541e091b1c91ec1038 Mon Sep 17 00:00:00 2001 From: johnvente Date: Fri, 10 Nov 2023 11:46:38 -0500 Subject: [PATCH 11/24] fix: adding not covered tests --- .../components/AssessmentsTable/constants.js | 91 ------------------- .../components/AssessmentsTable/index.jsx | 7 +- .../components/ResponsesList/index.test.jsx | 30 ++++++ 3 files changed, 32 insertions(+), 96 deletions(-) delete mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/constants.js diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/constants.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/constants.js deleted file mode 100644 index 77551df8..00000000 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/constants.js +++ /dev/null @@ -1,91 +0,0 @@ -export const receivedAssessmentData = [ - { - idAssessment: '55550453040', - reviewerName: 'Carlos Doe', - userName: 'carlos_doe_10', - email: 'carlos@email.com', - assessmentDate: '28-10-2023', - assessmentScores: [{ - id: 'aB3cD7eF', - type: 'Ideas', - quality: 'Fair', - rate: 1, - }, - { - id: '9GhI2jKl', - type: 'Content', - quality: 'Excellent', - rate: 5, - }], - problemStep: 'Staff', - feedback: 'This is working really well', - }, - { - idAssessment: '55550453042', - reviewerName: 'John Smith', - userName: 'john_smith_23', - email: 'john@email.com', - assessmentDate: '30-10-2023', - assessmentScores: [{ - id: 'M4nO8pQr', - type: 'Ideas', - quality: 'Good', - rate: 5, - }, - { - id: 'X5tU6vWx', - type: 'Content', - quality: 'Excellent', - rate: 5, - }], - problemStep: 'Training', - feedback: 'Great progress!', - }, - { - idAssessment: '55550453048', - reviewerName: 'Emily Johnson', - userName: 'emily_j', - email: 'emily@email.com', - assessmentDate: '29-10-2023', - assessmentScores: [{ - id: 'PqW0uVwX', - type: 'Ideas', - quality: null, - rate: null, - }, - { - id: 'ZabC12DE', - type: 'Content', - quality: null, - rate: null, - }], - problemStep: 'Peers', - feedback: 'Needs improvement in certain areas.', - }, - { - idAssessment: '55550453050', - reviewerName: 'Sarah Brown', - userName: 'sarah_brown_45', - email: 'sarah@email.com', - assessmentDate: '31-10-2023', - assessmentScores: [{ - id: 'Yz123456', - type: 'Ideas', - quality: 'Good', - rate: 3, - }, - { - id: 'L7iR9oSt', - type: 'Content', - quality: 'Excellent', - rate: 5, - }], - problemStep: 'Self', - feedback: 'Excellent work, keep it up!', - }, -]; - -export const givenAssessmentData = receivedAssessmentData.map(assessment => ({ - ...assessment, - learnerName: assessment.reviewerName, -})); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx index 7b4eb3b4..e0549eb0 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx @@ -47,7 +47,6 @@ export const AssessmentsTable = ({ ); - emailAddressCell = () => ( + emailAddressCell = ({ value }) => ( - email@example.com + {value} ); @@ -150,11 +151,18 @@ export class SubmissionsTable extends React.Component { { Header: this.userLabel, accessor: this.userAccessor, + filter: false, + }, + { + Header: this.translate(messages.learnerFullname), + accessor: submissionFields.fullname, + disableFilters: true, }, { Header: this.translate(messages.emailLabel), - accessor: null, + accessor: submissionFields.email, Cell: this.emailAddressCell, + disableFilters: true, }, { Header: this.dateSubmittedLabel, @@ -216,6 +224,7 @@ SubmissionsTable.propTypes = { })), loadSelectionForReview: PropTypes.func.isRequired, setProblemStepsModal: PropTypes.func.isRequired, + setProblemStepSetSelectedSubmissionId: PropTypes.func.isRequired, setActiveSubmissionIndex: PropTypes.func.isRequired, }; @@ -227,6 +236,7 @@ export const mapStateToProps = (state) => ({ export const mapDispatchToProps = { loadSelectionForReview: thunkActions.grading.loadSelectionForReview, setProblemStepsModal: actions.problemSteps.setOpenReviewModal, + setProblemStepSetSelectedSubmissionId: actions.problemSteps.setSelectedSubmissionId, setActiveSubmissionIndex: actions.grading.setActiveIndex, }; diff --git a/src/containers/ListView/SubmissionsTable.test.jsx b/src/containers/ListView/SubmissionsTable.test.jsx index 842f1a51..ac5f510b 100644 --- a/src/containers/ListView/SubmissionsTable.test.jsx +++ b/src/containers/ListView/SubmissionsTable.test.jsx @@ -190,15 +190,22 @@ describe('SubmissionsTable component', () => { accessor: submissionFields.username, }); }); - test('email column', () => { + + test('fullname column', () => { expect(columns[1]).toEqual({ + Header: messages.learnerFullname.defaultMessage, + accessor: submissionFields.fullname, + }); + }); + test('email column', () => { + expect(columns[2]).toEqual({ Header: messages.emailLabel.defaultMessage, - accessor: null, + accessor: submissionFields.email, Cell: el.instance().emailAddressCell, }); }); test('submission date column', () => { - expect(columns[2]).toEqual({ + expect(columns[3]).toEqual({ Header: messages.learnerSubmissionDate.defaultMessage, accessor: submissionFields.dateSubmitted, Cell: el.instance.children[0].props.columns[1].Cell, @@ -206,7 +213,7 @@ describe('SubmissionsTable component', () => { }); }); test('grade column', () => { - expect(columns[3]).toEqual({ + expect(columns[4]).toEqual({ Header: messages.grade.defaultMessage, accessor: submissionFields.score, Cell: el.instance.children[0].props.columns[2].Cell, @@ -214,7 +221,7 @@ describe('SubmissionsTable component', () => { }); }); test('grading status column', () => { - expect(columns[4]).toEqual({ + expect(columns[5]).toEqual({ Header: messages.gradingStatus.defaultMessage, accessor: submissionFields.gradingStatus, Cell: el.instance.children[0].props.columns[3].Cell, @@ -237,7 +244,7 @@ describe('SubmissionsTable component', () => { }); }); test('submission date column', () => { - expect(columns[2]).toEqual({ + expect(columns[3]).toEqual({ Header: messages.teamSubmissionDate.defaultMessage, accessor: submissionFields.dateSubmitted, Cell: el.instance.children[0].props.columns[1].Cell, @@ -245,7 +252,7 @@ describe('SubmissionsTable component', () => { }); }); test('grade column', () => { - expect(columns[3]).toEqual({ + expect(columns[4]).toEqual({ Header: messages.grade.defaultMessage, accessor: submissionFields.score, Cell: el.instance.children[0].props.columns[2].Cell, @@ -253,7 +260,7 @@ describe('SubmissionsTable component', () => { }); }); test('grading status column', () => { - expect(columns[4]).toEqual({ + expect(columns[5]).toEqual({ Header: messages.gradingStatus.defaultMessage, accessor: submissionFields.gradingStatus, Cell: el.instance.children[0].props.columns[3].Cell, diff --git a/src/containers/ListView/messages.js b/src/containers/ListView/messages.js index a16d9757..f4c3d2ba 100644 --- a/src/containers/ListView/messages.js +++ b/src/containers/ListView/messages.js @@ -38,9 +38,14 @@ const messages = defineMessages({ }, learnerSubmissionDate: { id: 'ora-grading.ListView.tableHeaders.learnerSubmissionDate', - defaultMessage: 'Learner submission date', + defaultMessage: 'Submission date', description: 'Learner submission date table column header for submission list view', }, + learnerFullname: { + id: 'ora-grading.ListView.tableHeaders.learnerFullname', + defaultMessage: 'Full name', + description: 'Learner full name table column header for submission list view', + }, teamSubmissionDate: { id: 'ora-grading.ListView.tableHeaders.teamSubmissionDate', defaultMessage: 'Team submission date', diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/ReviewProblemStepActions.scss b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/ReviewProblemStepActions.scss index 54793ce5..2170a086 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/ReviewProblemStepActions.scss +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/ReviewProblemStepActions.scss @@ -6,6 +6,15 @@ flex-direction: row; background-color: $light-200; + > * { + max-width: 300px; + word-wrap: break-word; + } + + &__info { + min-height: 100px; + } + .review-actions-username { flex-grow: 1; } diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.jsx index 3a024584..3fcc283b 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.jsx @@ -1,63 +1,73 @@ import React from 'react'; import { ActionRow } from '@edx/paragon'; +import PropTypes from 'prop-types'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import StatusBadge from 'components/StatusBadge'; import SubmissionNavigation from 'containers/ReviewActions/components/SubmissionNavigation'; +import messages from './messages'; import './ReviewProblemStepActions.scss'; -export const ReviewProblemStepActions = () => ( +export const ReviewProblemStepActions = ({ + intl, + fullname, + username, + email, + submissionId, + submissionDate, + grade, + gradingStatus, +}) => (
-

John Doe

-

jhon_20

+

+ {fullname} +

+

{username}

-

Email

-

jhonvente@email.com

+

+ {intl.formatMessage(messages.emailTitle)} +

+

{email}

-

Submission ID

-

483234704918

+

+ {intl.formatMessage(messages.submissionIdTitle)} +

+

{submissionId}

-

Submission date

-

9/13/2023, 7:13:56 AM

+

+ {intl.formatMessage(messages.submissionDateTitle)} +

+

{submissionDate}

-

Grade

-

3/10

+

+ {intl.formatMessage(messages.gradeTitle)} +

+

{grade}

-

Grading status

-

Upgraded

+

+ {intl.formatMessage(messages.gradingStatus)} +

+

{gradingStatus}

-

Problem steps

+

+ {intl.formatMessage(messages.problemStepsTitle)} +

- - - - + + + +

@@ -67,4 +77,15 @@ export const ReviewProblemStepActions = () => (
); -export default ReviewProblemStepActions; +ReviewProblemStepActions.propTypes = { + intl: intlShape.isRequired, + fullname: PropTypes.string.isRequired, + username: PropTypes.string.isRequired, + email: PropTypes.string.isRequired, + submissionId: PropTypes.string.isRequired, + submissionDate: PropTypes.string.isRequired, + grade: PropTypes.string.isRequired, + gradingStatus: PropTypes.string.isRequired, +}; + +export default injectIntl(ReviewProblemStepActions); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx index 906b6f91..2778ff85 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx @@ -1,33 +1,73 @@ import React from 'react'; import { shallow } from 'enzyme'; -import ReviewProblemStepActions from '.'; +import { formatMessage } from 'testUtils'; +import { ReviewProblemStepActions } from '.'; + +import messages from './messages'; describe('ReviewProblemStepActions component', () => { - it('renders without crashing', () => { - const wrapper = shallow(); - expect(wrapper.exists()).toBe(true); + const defaultProps = { + fullname: 'John Doe', + username: 'john_20', + email: 'johnvente@email.com', + submissionId: '483234704918', + submissionDate: '9/13/2023, 7:13:56 AM', + grade: '3/10', + gradingStatus: 'Upgraded', + }; + + let wrapper; + + beforeEach(() => { + wrapper = shallow(); }); - it('should render the component with correct content', () => { - const wrapper = shallow(); - - // Check if certain elements with expected text content exist in the rendered component - expect(wrapper.find('h3').text()).toEqual('John Doe'); - expect(wrapper.find('p').at(0).text()).toEqual('jhon_20'); - expect(wrapper.find('h4').at(0).text()).toEqual('Email'); - expect(wrapper.find('p').at(1).text()).toEqual('jhonvente@email.com'); - expect(wrapper.find('h4').at(1).text()).toEqual('Submission ID'); - expect(wrapper.find('p').at(2).text()).toEqual('483234704918'); - expect(wrapper.find('h4').at(2).text()).toEqual('Submission date'); - expect(wrapper.find('p').at(3).text()).toEqual('9/13/2023, 7:13:56 AM'); - expect(wrapper.find('h4').at(3).text()).toEqual('Grade'); - expect(wrapper.find('p').at(4).text()).toEqual('3/10'); - expect(wrapper.find('h4').at(4).text()).toEqual('Grading status'); - expect(wrapper.find('p').at(5).text()).toEqual('Upgraded'); - - // Check if StatusBadges with expected titles exist - expect(wrapper.find('StatusBadge').at(0).prop('title')).toEqual('Training'); - expect(wrapper.find('StatusBadge').at(1).prop('title')).toEqual('Peers'); - expect(wrapper.find('StatusBadge').at(2).prop('title')).toEqual('Self'); - expect(wrapper.find('StatusBadge').at(3).prop('title')).toEqual('Staff'); + + describe('Should render the correct titles', () => { + it('renders the correct email title', () => { + expect(wrapper.find('[data-testid="email-title"]').text()).toEqual( + formatMessage(messages.emailTitle), + ); + }); + + it('renders the correct submission ID title', () => { + expect(wrapper.find('[data-testid="submission-id-title"]').text()).toEqual( + formatMessage(messages.submissionIdTitle), + ); + }); + + it('renders the correct submission date title', () => { + expect(wrapper.find('[data-testid="submission-date-title"]').text()).toEqual( + formatMessage(messages.submissionDateTitle), + ); + }); + + it('renders the correct grade title', () => { + expect(wrapper.find('[data-testid="grade-title"]').text()).toEqual( + formatMessage(messages.gradeTitle), + ); + }); + + it('renders the correct grading status title', () => { + expect(wrapper.find('[data-testid="grade-status-title"]').text()).toEqual( + formatMessage(messages.gradingStatus), + ); + }); + + it('renders the correct problem steps title', () => { + expect(wrapper.find('[data-testid="problem-steps-title"]').text()).toEqual( + formatMessage(messages.problemStepsTitle), + ); + }); + }); + + it('renders the correct props data', () => { + expect(true).toBe(true); + expect(wrapper.find('[data-testid="fullname-value"]').text()).toEqual('John Doe'); + expect(wrapper.find('[data-testid="username-value"]').text()).toEqual('john_20'); + expect(wrapper.find('[data-testid="email-value"]').text()).toEqual(defaultProps.email); + expect(wrapper.find('[data-testid="submission-id-value"]').text()).toEqual(defaultProps.submissionId); + expect(wrapper.find('[data-testid="submission-date-value"]').text()).toEqual(defaultProps.submissionDate); + expect(wrapper.find('[data-testid="grade-value"]').text()).toEqual(defaultProps.grade); + expect(wrapper.find('[data-testid="grade-status-value"]').text()).toEqual(defaultProps.gradingStatus); }); }); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/messages.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/messages.js index d9bd8d90..61d1e21a 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/messages.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/messages.js @@ -1,22 +1,36 @@ -/* import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@edx/frontend-platform/i18n'; const messages = defineMessages({ - pointsDisplay: { - id: 'ora-grading.ReviewActions.pointsDisplay', - defaultMessage: 'Score: {pointsEarned}/{pointsPossible}', - description: 'Review pane action bar score display', + emailTitle: { + id: 'ora-grading.ReviewActions.emailTitle', + defaultMessage: 'Email', + description: 'Title for email field in review actions', }, - hideRubric: { - id: 'ora-grading.ReviewActions.hideRubric', - defaultMessage: 'Hide Rubric', - description: 'Review pane action bar Hide Rubric button text', + submissionIdTitle: { + id: 'ora-grading.ReviewActions.submissionIdTitle', + defaultMessage: 'Submission ID', + description: 'Title for submission ID field in review actions', }, - showRubric: { - id: 'ora-grading.ReviewActions.showRubric', - defaultMessage: 'Show Rubric', - description: 'Review pane action bar Show Rubric button text', + submissionDateTitle: { + id: 'ora-grading.ReviewActions.submissionDate', + defaultMessage: 'Submission date', + description: 'Title for submission date field in review actions', + }, + gradeTitle: { + id: 'ora-grading.ReviewActions.gradeTitle', + defaultMessage: 'Grade', + description: 'Title for grade field in review actions', + }, + gradingStatus: { + id: 'ora-grading.ReviewActions.gradingStatus', + defaultMessage: 'Grading status', + description: 'Title for grading status field in review actions', + }, + problemStepsTitle: { + id: 'ora-grading.ReviewActions.ProblemStepsTitle', + defaultMessage: 'Problem Steps', + description: 'Title for problem steps field in review actions', }, }); export default messages; - */ diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx index e0549eb0..1bf2cec5 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx @@ -11,7 +11,7 @@ import messages from './messages'; import './AssessmentsTable.scss'; export const AssessmentsTable = ({ - intl, assessmentsList, onClickReceivedAssessment, onClickGivenAssessment, + intl, assessmentsList, onClickReceivedAssessment, onClickGivenAssessment, isLoading, }) => { const [assessmentSelected, setAssessmentSelected] = useState( 'receivedAssessmentSelected', @@ -84,6 +84,7 @@ export const AssessmentsTable = ({ ( + + {title} +

+ {message} +

+
+); + +ErrorMessage.propTypes = { + title: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, +}; + +export default ErrorMessage; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.jsx index 13e5ffef..407d8c66 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.jsx @@ -1,22 +1,80 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { actions, selectors } from 'data/redux'; +import api from 'data/services/lms/api'; import ResponsesList from './components/ResponsesList'; import AssessmentsTable from './components/AssessmentsTable'; +import { assessmentTableFormat, responsesListFormat } from './utils'; +import ErrorMessage from './components/ErrorMessage'; +import messages from './messages'; import './ReviewProblemStepsContent.scss'; -export const ReviewProblemStepsContent = ({ toggleShowRubric }) => ( -
- - -
-); +export const ReviewProblemStepsContent = ({ + intl, toggleShowRubric, submissionUUID = '', hasDetailSubmissionError, responses, +}) => { + const [isLoadingFeedbackList, setIsLoadingFeedbackList] = useState(false); + const [feedbackList, setFeedbackList] = useState([]); + const [feedbackListError, setFeedbackListError] = useState(null); + const [feedbackListType, setFeedbackListType] = useState('received'); + const { text } = responses; + const responsesList = responsesListFormat(text); + + useEffect(() => { + const getFeedbackList = async () => { + setIsLoadingFeedbackList(true); + try { + const data = await api.getFeedbackList(submissionUUID, feedbackListType); + const { assessments } = data; + const formatData = assessmentTableFormat(assessments); + setFeedbackList(formatData); + } catch (error) { + setFeedbackListError('Error'); + } finally { + setIsLoadingFeedbackList(false); + } + }; + getFeedbackList(); + }, [feedbackListType, submissionUUID]); + + return ( +
+ { hasDetailSubmissionError ? ( + + ) : } + { feedbackListError && !isLoadingFeedbackList ? ( + + ) : ( + setFeedbackListType('received')} + onClickGivenAssessment={() => setFeedbackListType('given')} + /> + )} + +
+ ); +}; ReviewProblemStepsContent.propTypes = { toggleShowRubric: PropTypes.func.isRequired, + submissionUUID: PropTypes.string.isRequired, + hasDetailSubmissionError: PropTypes.string.isRequired, + responses: PropTypes.shape({ + text: PropTypes.arrayOf(PropTypes.string).isRequired, + files: PropTypes.arrayOf(PropTypes.string).isRequired, + }).isRequired, + intl: intlShape.isRequired, }; export const mapDispatchToProps = { @@ -27,4 +85,4 @@ export const mapStateToProps = (state) => ({ showRubric: selectors.app.showRubric(state), }); -export default connect(mapStateToProps, mapDispatchToProps)(ReviewProblemStepsContent); +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ReviewProblemStepsContent)); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/messages.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/messages.js new file mode 100644 index 00000000..6d6da666 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/messages.js @@ -0,0 +1,26 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + feedbackListTitleError: { + id: 'ora-grading.ReviewProblemStepsModal.ReviewProblemStepsContent.feedbackListTitleError', + defaultMessage: 'Error', + description: 'Error title when feedback list has failed', + }, + feedbackListMessageError: { + id: 'ora-grading.ReviewProblemStepsModal.ReviewProblemStepsContent.feedbackListMessageError', + defaultMessage: 'An unexpected error occurred', + description: 'Error message when feedback list has failed', + }, + responseListTitleError: { + id: 'ora-grading.ReviewProblemStepsModal.ReviewProblemStepsContent.responseListTitleError', + defaultMessage: 'Error', + description: 'Error title when response list has failed', + }, + responseListMessageError: { + id: 'ora-grading.ReviewProblemStepsModal.ReviewProblemStepsContent.responseListMessageError', + defaultMessage: 'An unexpected error occurred', + description: 'Error message when response list has failed', + }, +}); + +export default messages; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js new file mode 100644 index 00000000..bbe83036 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js @@ -0,0 +1,31 @@ +import { v4 as uuidv4 } from 'uuid'; + +export const assessmentTableFormat = (arr) => arr.map(({ + idAssessment, assesmentDate, scorerEmail, scorerName, scorerUsername, feedback, problemStep, assesmentScores, +}) => { + const newAssesmentScores = assesmentScores.map(({ criterionName, scoreEarned, scoreType }) => ({ + id: uuidv4(), type: criterionName, quality: scoreType, rate: scoreEarned, + })); + return { + idAssessment, + reviewerName: scorerName, + userName: scorerUsername, + email: scorerEmail, + assessmentDate: assesmentDate, + assessmentScores: newAssesmentScores, + feedback, + problemStep, + }; +}, []); + +export const responsesListFormat = (arr = []) => { + if (!Array.isArray(arr)) { + return []; + } + + return arr.map((response, index) => ({ + id: uuidv4(), + title: `Prompt ${index + 1}`, + response, + })); +}; diff --git a/src/containers/ReviewProblemStepsModal/hooks.js b/src/containers/ReviewProblemStepsModal/hooks.js index 14f1e482..285c3018 100644 --- a/src/containers/ReviewProblemStepsModal/hooks.js +++ b/src/containers/ReviewProblemStepsModal/hooks.js @@ -19,6 +19,8 @@ export const reduxValues = () => ({ selectors.requests.isCompleted(val, { requestKey: RequestKeys.fetchSubmission }) )), isModalOpen: useSelector(selectors.problemSteps.reviewModalOpen), + submissions: useSelector(selectors.submissions.allSubmissions), + currentSubmission: useSelector(selectors.grading.current), }); export const rendererHooks = ({ @@ -31,6 +33,8 @@ export const rendererHooks = ({ hasGradingProgress, isLoaded, isModalOpen, + submissions, + currentSubmission, } = module.reduxValues(); const onClose = () => { @@ -39,6 +43,7 @@ export const rendererHooks = ({ } else { dispatch(thunkActions.app.cancelReview()); dispatch(actions.problemSteps.setOpenReviewModal(false)); + dispatch(actions.problemSteps.setSelectedSubmissionId(null)); } }; @@ -46,6 +51,9 @@ export const rendererHooks = ({ onClose, isLoading: !(errorStatus || isLoaded), isModalOpen, + errorStatus, + submissions, + currentSubmission, closeConfirmModalProps: { isOpen: show, onCancel: () => setShow(false), diff --git a/src/containers/ReviewProblemStepsModal/index.jsx b/src/containers/ReviewProblemStepsModal/index.jsx index ab3c8158..b647c3a5 100644 --- a/src/containers/ReviewProblemStepsModal/index.jsx +++ b/src/containers/ReviewProblemStepsModal/index.jsx @@ -12,6 +12,7 @@ import CloseReviewConfirmModal from './components/CloseReviewConfirmModal'; import messages from './messages'; import * as hooks from './hooks'; import './ReviewProblemStepsModal.scss'; +import { transformObjectToDetail } from './utils'; /** * @@ -23,15 +24,23 @@ export const ReviewProblemStepsModal = () => { onClose, isModalOpen, closeConfirmModalProps, + errorStatus, + submissions, + currentSubmission, } = hooks.rendererHooks({ dispatch }); + const { submissionUUID, response } = currentSubmission; + const submissionData = submissions[submissionUUID] || {}; + const stepProblemDetail = submissions[submissionUUID] ? transformObjectToDetail(submissionData) : {}; + const hasDetailSubmissionError = typeof errorStatus !== 'undefined'; + return ( - + { submissionData && } )} @@ -39,7 +48,13 @@ export const ReviewProblemStepsModal = () => { className="review-modal" modalBodyClassName="review-step-problems-modal-body" > - {isModalOpen && } + {isModalOpen && submissionUUID && ( + + )} {/* even if the modal is closed, in case we want to add transitions later */} {isLoading && } diff --git a/src/containers/ReviewProblemStepsModal/utils.js b/src/containers/ReviewProblemStepsModal/utils.js new file mode 100644 index 00000000..5294b52a --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/utils.js @@ -0,0 +1,30 @@ +/* eslint-disable import/prefer-default-export */ +import moment from 'moment'; + +export const formatDate = (value = '') => { + const date = new Date(moment(value)); + return date.toLocaleString(); +}; + +const formatGrade = (score) => (score === null ? '-' : `${score.pointsEarned}/${score.pointsPossible}`); + +const capitalizeFirstLetter = (str = '') => (str ? `${str.charAt(0).toUpperCase()}${str.slice(1)}` : ''); + +export const transformObjectToDetail = ({ + fullname = '', + username = '', + email = '', + submissionUUID = '', + dateSubmitted = '', + score, + gradeStatus = '', +}) => ({ + fullname, + username, + email, + submissionId: submissionUUID, + submissionDate: formatDate(dateSubmitted), + grade: formatGrade(score), + gradingStatus: capitalizeFirstLetter(gradeStatus), + +}); diff --git a/src/data/redux/problem-steps/reducer.js b/src/data/redux/problem-steps/reducer.js index ee0ee07e..596a5c17 100644 --- a/src/data/redux/problem-steps/reducer.js +++ b/src/data/redux/problem-steps/reducer.js @@ -4,6 +4,7 @@ import { StrictDict } from 'utils'; const initialState = { reviewModalOpen: false, + selectedSubmissionId: null, }; const problemSteps = createSlice({ @@ -11,6 +12,7 @@ const problemSteps = createSlice({ initialState, reducers: { setOpenReviewModal: (state, { payload }) => ({ ...state, reviewModalOpen: payload }), + setSelectedSubmissionId: (state, { payload }) => ({ ...state, selectedSubmissionId: payload }), }, }); diff --git a/src/data/services/lms/api.js b/src/data/services/lms/api.js index bc4c41ce..dbb8eb3f 100644 --- a/src/data/services/lms/api.js +++ b/src/data/services/lms/api.js @@ -1,4 +1,5 @@ import { StrictDict } from 'utils'; +import { camelizeKeys } from 'utils/objectUtils'; import { locationId } from 'data/constants/app'; import { paramKeys } from './constants'; import urls from './urls'; @@ -130,6 +131,18 @@ const updateGrade = (submissionUUID, gradeData) => post( gradeData, ).then(response => response.data); +/* + * get('api/assessments/feedback', { submissionUUID, assessmentType }) + * @param {object} gradeData - full grading submission data + */ +const getFeedbackList = (submissionUUID, assessmentType) => get( + stringifyUrl(urls.getFeedbackSubmissionsUrl(), { + [paramKeys.oraLocation]: locationId(), + [paramKeys.submissionUUID]: submissionUUID, + [paramKeys.assessmentType]: assessmentType, + }), +).then(response => camelizeKeys(response.data)); + export default StrictDict({ initializeApp, fetchSubmission, @@ -137,6 +150,7 @@ export default StrictDict({ fetchSubmissionStatus, lockSubmission, updateGrade, + getFeedbackList, unlockSubmission, batchUnlockSubmissions, }); diff --git a/src/data/services/lms/constants.js b/src/data/services/lms/constants.js index e6713be0..3ce1541e 100644 --- a/src/data/services/lms/constants.js +++ b/src/data/services/lms/constants.js @@ -33,6 +33,7 @@ export const fileUploadResponseOptions = StrictDict({ export const paramKeys = StrictDict({ oraLocation: 'oraLocation', submissionUUID: 'submissionUUID', + assessmentType: 'assessmentType', }); export const oraTypes = StrictDict({ @@ -46,5 +47,7 @@ export const submissionFields = StrictDict({ score: 'score', teamName: 'teamName', username: 'username', + email: 'email', + fullname: 'fullname', problemSteps: 'problemSteps', }); diff --git a/src/data/services/lms/urls.js b/src/data/services/lms/urls.js index de95808b..4d208455 100644 --- a/src/data/services/lms/urls.js +++ b/src/data/services/lms/urls.js @@ -13,6 +13,7 @@ const fetchSubmissionStatusUrl = () => `${baseEsgUrl()}submission/status`; const fetchSubmissionLockUrl = () => `${baseEsgUrl()}submission/lock`; const batchUnlockSubmissionsUrl = () => `${baseEsgUrl()}submission/batch/unlock`; const updateSubmissionGradeUrl = () => `${baseEsgUrl()}submission/grade`; +const getFeedbackSubmissionsUrl = () => `${baseEsgUrl()}assessments/feedback`; const course = (courseId) => `${baseUrl()}/courses/${courseId}`; @@ -34,4 +35,5 @@ export default StrictDict({ course, openResponse, ora, + getFeedbackSubmissionsUrl, }); diff --git a/src/utils/objectUtils.js b/src/utils/objectUtils.js new file mode 100644 index 00000000..41c1196c --- /dev/null +++ b/src/utils/objectUtils.js @@ -0,0 +1,16 @@ +import _ from 'lodash'; + +export const camelizeKeys = (obj) => { + if (Array.isArray(obj)) { + return obj.map(v => camelizeKeys(v)); + } if (obj != null && obj.constructor === Object) { + return Object.keys(obj).reduce( + (result, key) => ({ + ...result, + [_.camelCase(key)]: camelizeKeys(obj[key]), + }), + {}, + ); + } + return obj; +}; From 7d9f6e64872039cfd9633c07c936269696e8004a Mon Sep 17 00:00:00 2001 From: johnvente Date: Thu, 23 Nov 2023 11:25:27 -0500 Subject: [PATCH 13/24] refactor: adding more unit tests for all --- .eslintrc.js | 12 +-- package.json | 2 - src/containers/ListView/SubmissionsTable.jsx | 3 - .../ListView/SubmissionsTable.test.jsx | 4 + .../components/ErrorMessage/index.jsx | 4 +- .../components/ErrorMessage/index.test.jsx | 51 ++++++++++ .../ReviewProblemStepsContent/hooks.js | 35 +++++++ .../ReviewProblemStepsContent/hooks.test.js | 34 +++++++ .../ReviewProblemStepsContent/index.jsx | 41 +++----- .../ReviewProblemStepsContent/index.test.jsx | 29 +++++- .../ReviewProblemStepsContent/utils.js | 10 ++ .../ReviewProblemStepsContent/utils.test.js | 68 ++++++++++++++ .../ReviewProblemStepsModal/hooks.js | 1 - .../ReviewProblemStepsModal/hooks.test.js | 13 +++ .../ReviewProblemStepsModal/index.test.jsx | 3 + .../ReviewProblemStepsModal/utils.js | 31 +++++- .../ReviewProblemStepsModal/utils.test.js | 94 +++++++++++++++++++ src/data/redux/submissions/selectors.test.js | 2 - src/data/services/lms/api.js | 2 +- src/data/services/lms/api.test.js | 15 +++ src/utils/objectUtils.js | 5 + src/utils/objectUtils.test.js | 73 ++++++++++++++ 22 files changed, 482 insertions(+), 50 deletions(-) create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.test.js create mode 100644 src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.test.js create mode 100644 src/containers/ReviewProblemStepsModal/utils.test.js create mode 100644 src/utils/objectUtils.test.js diff --git a/.eslintrc.js b/.eslintrc.js index 165a1b2f..8b10d815 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,11 +6,11 @@ const config = createConfig('eslint', { 'import/no-named-as-default': 'off', 'import/no-named-as-default-member': 'off', 'import/no-import-module-exports': 'off', - 'import/prefer-default-export': "off", + 'import/prefer-default-export': 'off', 'import/no-self-import': 'off', - 'spaced-comment': ['error', 'always', { 'block': { 'exceptions': ['*'] } }], + 'spaced-comment': ['error', 'always', { block: { exceptions: ['*'] } }], 'react-hooks/rules-of-hooks': 'off', - "react/forbid-prop-types": ["error", { "forbid": ["any", "array"] }], // arguable object proptype is use when I do not care about the shape of the object + 'react/forbid-prop-types': ['error', { forbid: ['any', 'array'] }], // arguable object proptype is use when I do not care about the shape of the object 'no-import-assign': 'off', 'no-promise-executor-return': 'off', 'import/no-cycle': 'off', @@ -18,10 +18,10 @@ const config = createConfig('eslint', { }); config.settings = { - "import/resolver": { + 'import/resolver': { node: { - paths: ["src", "node_modules"], - extensions: [".js", ".jsx"], + paths: ['src', 'node_modules'], + extensions: ['.js', '.jsx'], }, }, }; diff --git a/package.json b/package.json index 6c6a2957..5ce63086 100755 --- a/package.json +++ b/package.json @@ -14,8 +14,6 @@ "semantic-release": "semantic-release", "start": "fedx-scripts webpack-dev-server --progress", "test": "TZ=GMT fedx-scripts jest --coverage --passWithNoTests", - "test:unit": "TZ=GMT fedx-scripts jest src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions --coverage --passWithNoTests", - "test:update": "npm test -- --updateSnapsho", "watch-tests": "jest --watch", "prepare": "husky install" }, diff --git a/src/containers/ListView/SubmissionsTable.jsx b/src/containers/ListView/SubmissionsTable.jsx index ac33aca3..32499d0e 100644 --- a/src/containers/ListView/SubmissionsTable.jsx +++ b/src/containers/ListView/SubmissionsTable.jsx @@ -95,7 +95,6 @@ export class SubmissionsTable extends React.Component { this.props.loadSelectionForReview(submissionUUIDs, false, submissionId); this.props.setActiveSubmissionIndex(currentRowIndex); this.props.setProblemStepsModal(true); - this.props.setProblemStepSetSelectedSubmissionId(submissionId); }; problemStepsViewDetails = ({ data, row: currentRow }) => ( @@ -224,7 +223,6 @@ SubmissionsTable.propTypes = { })), loadSelectionForReview: PropTypes.func.isRequired, setProblemStepsModal: PropTypes.func.isRequired, - setProblemStepSetSelectedSubmissionId: PropTypes.func.isRequired, setActiveSubmissionIndex: PropTypes.func.isRequired, }; @@ -236,7 +234,6 @@ export const mapStateToProps = (state) => ({ export const mapDispatchToProps = { loadSelectionForReview: thunkActions.grading.loadSelectionForReview, setProblemStepsModal: actions.problemSteps.setOpenReviewModal, - setProblemStepSetSelectedSubmissionId: actions.problemSteps.setSelectedSubmissionId, setActiveSubmissionIndex: actions.grading.setActiveIndex, }; diff --git a/src/containers/ListView/SubmissionsTable.test.jsx b/src/containers/ListView/SubmissionsTable.test.jsx index ac5f510b..e184f337 100644 --- a/src/containers/ListView/SubmissionsTable.test.jsx +++ b/src/containers/ListView/SubmissionsTable.test.jsx @@ -188,6 +188,7 @@ describe('SubmissionsTable component', () => { expect(columns[0]).toEqual({ Header: messages.username.defaultMessage, accessor: submissionFields.username, + filter: false, }); }); @@ -195,6 +196,7 @@ describe('SubmissionsTable component', () => { expect(columns[1]).toEqual({ Header: messages.learnerFullname.defaultMessage, accessor: submissionFields.fullname, + disableFilters: true, }); }); test('email column', () => { @@ -202,6 +204,7 @@ describe('SubmissionsTable component', () => { Header: messages.emailLabel.defaultMessage, accessor: submissionFields.email, Cell: el.instance().emailAddressCell, + disableFilters: true, }); }); test('submission date column', () => { @@ -241,6 +244,7 @@ describe('SubmissionsTable component', () => { expect(columns[0]).toEqual({ Header: messages.teamName.defaultMessage, accessor: submissionFields.teamName, + filter: false, }); }); test('submission date column', () => { diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.jsx index 15252d69..f13af98f 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.jsx @@ -8,8 +8,8 @@ export const ErrorMessage = ({ title, message }) => ( icon={Info} stacked > - {title} -

+ {title} +

{message}

diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx index e69de29b..6b6e4755 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { Alert } from '@edx/paragon'; +import { Info } from '@edx/paragon/icons'; +import ErrorMessage from '.'; + +describe('ErrorMessage component', () => { + const defaultProps = { + title: 'Error Title', + message: 'Error Message', + }; + let wrapper; + + beforeEach(() => { + wrapper = shallow(); + }); + + it('renders without crashing', () => { + expect(wrapper.exists()).toBe(true); + }); + + it('renders an Alert component', () => { + expect(wrapper.find(Alert)).toHaveLength(1); + }); + + it('renders Alert with correct props', () => { + const alertComponent = wrapper.find(Alert); + + expect(alertComponent.prop('variant')).toBe('danger'); + expect(alertComponent.prop('icon')).toBe(Info); + expect(alertComponent.prop('stacked')).toBe(true); + }); + + it('renders Alert.Heading with provided title', () => { + const title = 'Error Title Testing'; + const message = 'Error Message'; + wrapper = shallow(); + const alertHeading = wrapper.find('[data-testid="title-heading"]'); + expect(alertHeading.exists()).toBe(true); + expect(alertHeading.text()).toBe(title); + }); + + it('renders message within the Alert', () => { + const title = 'Error Title'; + const message = 'Error Message Testing'; + wrapper = shallow(); + const alertMessage = wrapper.find('[data-testid="message"]'); + expect(alertMessage.exists()).toBe(true); + expect(alertMessage.text()).toBe(message); + }); +}); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js new file mode 100644 index 00000000..5afb5811 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js @@ -0,0 +1,35 @@ +import { useState, useEffect } from 'react'; +import api from 'data/services/lms/api'; +import { assessmentTableFormat } from './utils'; + +export const useFeedbackList = (submissionUUID) => { + const [isLoadingFeedbackList, setIsLoadingFeedbackList] = useState(false); + const [feedbackList, setFeedbackList] = useState([]); + const [feedbackListError, setFeedbackListError] = useState(null); + const [feedbackListType, setFeedbackListType] = useState('received'); + + useEffect(() => { + const getFeedbackList = async () => { + setIsLoadingFeedbackList(true); + try { + const data = await api.getFeedbackList(submissionUUID, feedbackListType); + const { assessments } = data; + const formatData = assessmentTableFormat(assessments); + setFeedbackList(formatData); + } catch (error) { + setFeedbackListError('Error'); + } finally { + setIsLoadingFeedbackList(false); + } + }; + getFeedbackList(); + }, [feedbackListType, submissionUUID]); + + return { + isLoadingFeedbackList, + feedbackList, + feedbackListError, + feedbackListType, + setFeedbackListType, + }; +}; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.test.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.test.js new file mode 100644 index 00000000..0d2fe942 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.test.js @@ -0,0 +1,34 @@ +import { useFeedbackList } from './hooks'; // Import your hook + +jest.mock('./hooks'); + +describe('useFeedbackList hook', () => { + it('Should returns correctly data', async () => { + const hookMock = { + isLoadingFeedbackList: true, + feedbackList: [], + feedbackListError: null, + feedbackListType: 'received', + setFeedbackListType: jest.fn(), + }; + useFeedbackList.mockReturnValue(hookMock); + const hookReturn = useFeedbackList('sampleUUID'); + expect(hookMock).toBe(hookReturn); + }); + + it('Should call setFeedbackListType correctly', async () => { + const hookMock = { + isLoadingFeedbackList: true, + feedbackList: [], + feedbackListError: null, + feedbackListType: 'received', + setFeedbackListType: jest.fn(), + }; + + useFeedbackList.mockReturnValue(hookMock); + const hookReturn = useFeedbackList('sampleUUID'); + hookReturn.setFeedbackListType(); + expect(hookMock).toBe(hookReturn); + expect(hookMock.setFeedbackListType).toBeCalled(); + }); +}); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.jsx index 407d8c66..d2bcd448 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.jsx @@ -1,45 +1,30 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { actions, selectors } from 'data/redux'; -import api from 'data/services/lms/api'; import ResponsesList from './components/ResponsesList'; import AssessmentsTable from './components/AssessmentsTable'; -import { assessmentTableFormat, responsesListFormat } from './utils'; +import { responsesListFormat } from './utils'; import ErrorMessage from './components/ErrorMessage'; import messages from './messages'; import './ReviewProblemStepsContent.scss'; +import { useFeedbackList } from './hooks'; export const ReviewProblemStepsContent = ({ - intl, toggleShowRubric, submissionUUID = '', hasDetailSubmissionError, responses, + intl, toggleShowRubric, submissionUUID, hasDetailSubmissionError, responses, }) => { - const [isLoadingFeedbackList, setIsLoadingFeedbackList] = useState(false); - const [feedbackList, setFeedbackList] = useState([]); - const [feedbackListError, setFeedbackListError] = useState(null); - const [feedbackListType, setFeedbackListType] = useState('received'); + const { + isLoadingFeedbackList, + feedbackList, + feedbackListError, + setFeedbackListType, + } = useFeedbackList(submissionUUID); const { text } = responses; const responsesList = responsesListFormat(text); - useEffect(() => { - const getFeedbackList = async () => { - setIsLoadingFeedbackList(true); - try { - const data = await api.getFeedbackList(submissionUUID, feedbackListType); - const { assessments } = data; - const formatData = assessmentTableFormat(assessments); - setFeedbackList(formatData); - } catch (error) { - setFeedbackListError('Error'); - } finally { - setIsLoadingFeedbackList(false); - } - }; - getFeedbackList(); - }, [feedbackListType, submissionUUID]); - return (
{ hasDetailSubmissionError ? ( @@ -66,10 +51,14 @@ export const ReviewProblemStepsContent = ({ ); }; +ReviewProblemStepsContent.defaultProps = { + hasDetailSubmissionError: false, +}; + ReviewProblemStepsContent.propTypes = { toggleShowRubric: PropTypes.func.isRequired, submissionUUID: PropTypes.string.isRequired, - hasDetailSubmissionError: PropTypes.string.isRequired, + hasDetailSubmissionError: PropTypes.bool, responses: PropTypes.shape({ text: PropTypes.arrayOf(PropTypes.string).isRequired, files: PropTypes.arrayOf(PropTypes.string).isRequired, diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.test.jsx index 5f8bf766..d5ab91c7 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.test.jsx @@ -1,32 +1,51 @@ import React from 'react'; import { shallow } from 'enzyme'; - +import { formatMessage } from 'testUtils'; import { ReviewProblemStepsContent, mapStateToProps } from '.'; import ResponsesList from './components/ResponsesList'; import AssessmentsTable from './components/AssessmentsTable'; +import ErrorMessage from './components/ErrorMessage'; describe('ReviewProblemStepsContent component', () => { + const defaultResponses = { text: [], files: [] }; + + const createWrapper = (additionalProps = {}) => { + const defaultProps = { + toggleShowRubric: () => {}, + intl: { formatMessage }, + submissionUUID: 'any-id', + responses: defaultResponses, + ...additionalProps, + }; + return shallow(); + }; + it('renders without crashing', () => { - const wrapper = shallow( {}} />); + const wrapper = createWrapper(); expect(wrapper.exists()).toBe(true); }); it('renders ResponsesList component', () => { - const wrapper = shallow( {}} />); + const wrapper = createWrapper(); expect(wrapper.find(ResponsesList).length).toBe(1); }); it('renders AssessmentsTable component', () => { - const wrapper = shallow( {}} />); + const wrapper = createWrapper(); expect(wrapper.find(AssessmentsTable).length).toBe(1); }); it('passes toggleShowRubric prop', () => { const toggleShowRubric = jest.fn(); - const wrapper = shallow(); + const wrapper = createWrapper({ toggleShowRubric }); const responsesList = wrapper.find(ResponsesList); expect(responsesList.prop('toggleShowRubric')).toBe(toggleShowRubric); }); + + it('renders ErrorMessage when hasDetailSubmissionError is true', () => { + const wrapper = createWrapper({ hasDetailSubmissionError: true }); + expect(wrapper.find(ErrorMessage).exists()).toBe(true); + }); }); describe('mapStateToProps', () => { diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js index bbe83036..2f81e4d5 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js @@ -1,5 +1,10 @@ import { v4 as uuidv4 } from 'uuid'; +/** + * Formats an array of assessment data into a specific structure. + * @param {Array} arr - The array containing assessment data. + * @returns {Array} - Returns an array of formatted assessment data. + */ export const assessmentTableFormat = (arr) => arr.map(({ idAssessment, assesmentDate, scorerEmail, scorerName, scorerUsername, feedback, problemStep, assesmentScores, }) => { @@ -18,6 +23,11 @@ export const assessmentTableFormat = (arr) => arr.map(({ }; }, []); +/** + * Formats an array of responses into a specific structure. + * @param {Array} [arr=[]] - The array containing responses data. + * @returns {Array} - Returns an array of formatted responses. + */ export const responsesListFormat = (arr = []) => { if (!Array.isArray(arr)) { return []; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.test.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.test.js new file mode 100644 index 00000000..6398ddcc --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.test.js @@ -0,0 +1,68 @@ +import { assessmentTableFormat, responsesListFormat } from './utils'; + +describe('assessmentTableFormat', () => { + it('formats assessment data correctly', () => { + const inputAssessmentData = [ + { + idAssessment: 1, + assesmentDate: '2023-11-17', + scorerEmail: 'scorer@example.com', + scorerName: 'Scorer 1', + scorerUsername: 'scorer1', + feedback: 'Good work!', + problemStep: 'Problem Step 1', + assesmentScores: [ + { criterionName: 'Criterion 1', scoreEarned: 8, scoreType: 'High' }, + { criterionName: 'Criterion 2', scoreEarned: 6, scoreType: 'Medium' }, + ], + }, + ]; + + const formattedAssessmentData = assessmentTableFormat(inputAssessmentData); + + expect(formattedAssessmentData).toEqual([ + { + idAssessment: 1, + reviewerName: 'Scorer 1', + userName: 'scorer1', + email: 'scorer@example.com', + assessmentDate: '2023-11-17', + assessmentScores: [ + { + id: expect.any(String), type: 'Criterion 1', quality: 'High', rate: 8, + }, + { + id: expect.any(String), type: 'Criterion 2', quality: 'Medium', rate: 6, + }, + ], + feedback: 'Good work!', + problemStep: 'Problem Step 1', + }, + + ]); + }); +}); + +describe('responsesListFormat', () => { + it('formats responses data correctly', () => { + const inputResponsesData = [ + 'Response 1', + 'Response 2', + ]; + + const formattedResponsesData = responsesListFormat(inputResponsesData); + + expect(formattedResponsesData).toEqual([ + { id: expect.any(String), title: 'Prompt 1', response: 'Response 1' }, + { id: expect.any(String), title: 'Prompt 2', response: 'Response 2' }, + ]); + }); + + it('returns an empty array for invalid input', () => { + const invalidInput = 'Invalid'; + + const formattedResponsesData = responsesListFormat(invalidInput); + + expect(formattedResponsesData).toEqual([]); + }); +}); diff --git a/src/containers/ReviewProblemStepsModal/hooks.js b/src/containers/ReviewProblemStepsModal/hooks.js index 285c3018..8178fcec 100644 --- a/src/containers/ReviewProblemStepsModal/hooks.js +++ b/src/containers/ReviewProblemStepsModal/hooks.js @@ -43,7 +43,6 @@ export const rendererHooks = ({ } else { dispatch(thunkActions.app.cancelReview()); dispatch(actions.problemSteps.setOpenReviewModal(false)); - dispatch(actions.problemSteps.setSelectedSubmissionId(null)); } }; diff --git a/src/containers/ReviewProblemStepsModal/hooks.test.js b/src/containers/ReviewProblemStepsModal/hooks.test.js index 3c824b2c..be929cef 100644 --- a/src/containers/ReviewProblemStepsModal/hooks.test.js +++ b/src/containers/ReviewProblemStepsModal/hooks.test.js @@ -17,6 +17,13 @@ jest.mock('data/redux', () => ({ selected: { response: (...args) => ({ selectedResponse: args }), }, + current: { + submissionUUID: 'random-id', + response: { + text: [], + files: [], + }, + }, }, requests: { isCompleted: (...args) => ({ isCompleted: args }), @@ -25,6 +32,9 @@ jest.mock('data/redux', () => ({ problemSteps: { reviewModalOpen: (...args) => ({ reviewModalOpen: args }), }, + submissions: { + allSubmissions: (...args) => ({ allSubmissions: args }), + }, }, actions: { app: { @@ -83,6 +93,9 @@ describe('ReviewModal hooks', () => { test('hasGradingProgress loads grading.hasGradingProgress', () => { testSelector(reduxKeys.hasGradingProgress, selectors.grading.hasGradingProgress); }); + test('allSubmissions loads submissions.allSubmissions', () => { + testSelector(reduxKeys.submissions, selectors.submissions.allSubmissions); + }); }); describe('non-state hooks', () => { beforeEach(state.mock); diff --git a/src/containers/ReviewProblemStepsModal/index.test.jsx b/src/containers/ReviewProblemStepsModal/index.test.jsx index 251c5aca..ff7f85cb 100644 --- a/src/containers/ReviewProblemStepsModal/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/index.test.jsx @@ -22,6 +22,9 @@ jest.mock('data/redux', () => ({ isCompleted: (...args) => ({ isCompleted: args }), errorStatus: (...args) => ({ errorStatus: args }), }, + submissions: { + allSubmissions: (...args) => ({ allSubmissions: args }), + }, problemSteps: { reviewModalOpen: (...args) => ({ reviewModalOpen: args }), }, diff --git a/src/containers/ReviewProblemStepsModal/utils.js b/src/containers/ReviewProblemStepsModal/utils.js index 5294b52a..2e251a00 100644 --- a/src/containers/ReviewProblemStepsModal/utils.js +++ b/src/containers/ReviewProblemStepsModal/utils.js @@ -1,15 +1,42 @@ /* eslint-disable import/prefer-default-export */ import moment from 'moment'; +/** + * Formats the provided date value into a localized string. + * @param {string} [value=''] - The date string to format. + * @returns {string} - The formatted date string in a localized format. + */ export const formatDate = (value = '') => { const date = new Date(moment(value)); return date.toLocaleString(); }; -const formatGrade = (score) => (score === null ? '-' : `${score.pointsEarned}/${score.pointsPossible}`); +/** + * Formats the grade score or returns a placeholder '-' if the score is null. + * @param {object|null} score - The score object containing pointsEarned and pointsPossible. + * @returns {string} - The formatted grade string or '-' if the score is null. + */ +export const formatGrade = (score) => (!score ? '-' : `${score.pointsEarned}/${score.pointsPossible}`); -const capitalizeFirstLetter = (str = '') => (str ? `${str.charAt(0).toUpperCase()}${str.slice(1)}` : ''); +/** + * Capitalizes the first letter of the provided string. + * @param {string} [str=''] - The string to capitalize. + * @returns {string} - The string with the first letter capitalized. + */ +export const capitalizeFirstLetter = (str = '') => (str ? `${str.charAt(0).toUpperCase()}${str.slice(1)}` : ''); +/** + * Transforms an object to a detail object with specific key transformations and formatting. + * @param {object} param0 - The object containing details. + * @param {string} [param0.fullname=''] - The full name. + * @param {string} [param0.username=''] - The username. + * @param {string} [param0.email=''] - The email address. + * @param {string} [param0.submissionUUID=''] - The submission UUID. + * @param {string} [param0.dateSubmitted=''] - The date of submission. + * @param {object|null} param0.score - The score object containing pointsEarned and pointsPossible. + * @param {string} [param0.gradeStatus=''] - The grade status. + * @returns {object} - The transformed detail object. + */ export const transformObjectToDetail = ({ fullname = '', username = '', diff --git a/src/containers/ReviewProblemStepsModal/utils.test.js b/src/containers/ReviewProblemStepsModal/utils.test.js new file mode 100644 index 00000000..3e090443 --- /dev/null +++ b/src/containers/ReviewProblemStepsModal/utils.test.js @@ -0,0 +1,94 @@ +import { + formatDate, formatGrade, capitalizeFirstLetter, transformObjectToDetail, +} from './utils'; + +describe('formatDate util', () => { + it('formats date string into a localized string', () => { + const date = '2023-11-17T08:30:00Z'; + const formattedDate = formatDate(date); + expect(formattedDate).toBe(new Date(date).toLocaleString()); + }); + + it('handles empty date string', () => { + const formattedDate = formatDate(); + expect(formattedDate).toBe(new Date('').toLocaleString()); + }); +}); + +describe('formatGrade util', () => { + it('formats score object into a grade string', () => { + const score = { pointsEarned: 85, pointsPossible: 100 }; + const formattedGrade = formatGrade(score); + expect(formattedGrade).toBe('85/100'); + }); + + it('handles null score object', () => { + const formattedGrade = formatGrade(null); + expect(formattedGrade).toBe('-'); + }); +}); + +describe('capitalizeFirstLetter util', () => { + it('capitalizes the first letter of a string', () => { + const str = 'test'; + const capitalizedStr = capitalizeFirstLetter(str); + expect(capitalizedStr).toBe('Test'); + }); + + it('handles empty string', () => { + const capitalizedStr = capitalizeFirstLetter(); + expect(capitalizedStr).toBe(''); + }); + + it('handles already capitalized util', () => { + const str = 'Test'; + const capitalizedStr = capitalizeFirstLetter(str); + expect(capitalizedStr).toBe('Test'); + }); +}); + +describe('transformObjectToDetail util', () => { + it('transforms object details as expected', () => { + const inputObject = { + fullname: 'John Doe', + username: 'johndoe123', + email: 'john@example.com', + submissionUUID: '123456789', + dateSubmitted: '2023-11-17T08:30:00Z', + score: { pointsEarned: 85, pointsPossible: 100 }, + gradeStatus: 'complete', + }; + + const expectedOutput = { + fullname: 'John Doe', + username: 'johndoe123', + email: 'john@example.com', + submissionId: '123456789', + submissionDate: new Date('2023-11-17T08:30:00Z').toLocaleString(), + grade: '85/100', + gradingStatus: 'Complete', + }; + + const transformedObject = transformObjectToDetail(inputObject); + expect(transformedObject).toEqual(expectedOutput); + }); + + it('handles missing or empty properties in the input object', () => { + const inputObject = { + fullname: 'Jane Doe', + }; + + const expectedOutput = { + fullname: 'Jane Doe', + username: '', + email: '', + submissionId: '', + submissionDate: new Date('').toLocaleString(), + grade: '-', + gradingStatus: '', + }; + + const transformedObject = transformObjectToDetail(inputObject); + expect(transformedObject).toEqual(expectedOutput); + }); +}); diff --git a/src/data/redux/submissions/selectors.test.js b/src/data/redux/submissions/selectors.test.js index 74f6c20f..745bbe6e 100644 --- a/src/data/redux/submissions/selectors.test.js +++ b/src/data/redux/submissions/selectors.test.js @@ -1,13 +1,11 @@ import { lockStatuses } from 'data/services/lms/constants'; -// import * in order to mock in-file references import * as selectors from './selectors'; jest.mock('reselect', () => ({ createSelector: jest.fn((preSelectors, cb) => ({ preSelectors, cb })), })); -// Test state for submissions const testState = { submissions: { allSubmissions: { diff --git a/src/data/services/lms/api.js b/src/data/services/lms/api.js index dbb8eb3f..f8571b91 100644 --- a/src/data/services/lms/api.js +++ b/src/data/services/lms/api.js @@ -133,7 +133,7 @@ const updateGrade = (submissionUUID, gradeData) => post( /* * get('api/assessments/feedback', { submissionUUID, assessmentType }) - * @param {object} gradeData - full grading submission data + * @param {object} feedbackData - full grading feedback of submission data */ const getFeedbackList = (submissionUUID, assessmentType) => get( stringifyUrl(urls.getFeedbackSubmissionsUrl(), { diff --git a/src/data/services/lms/api.test.js b/src/data/services/lms/api.test.js index ce2dbb25..141375e5 100644 --- a/src/data/services/lms/api.test.js +++ b/src/data/services/lms/api.test.js @@ -19,6 +19,7 @@ jest.mock('data/constants/app', () => ({ const gradeData = 'test-grade-data'; const submissionUUID = 'test-submission-uuid'; const submissionUUIDs = ['some', 'submission', 'uuid']; +const assessmentType = 'received'; const methodKeys = StrictDict({ get: 'get', @@ -150,4 +151,18 @@ describe('lms service api methods', () => { }, }); }); + + describe('getFeedbackList', () => { + testAPI({ + promise: api.getFeedbackList(submissionUUID, assessmentType), + method: methodKeys.get, + expected: { + urlKey: urlKeys.getFeedbackSubmissionsUrl, + urlParams: { + [paramKeys.oraLocation]: locationId(), + [paramKeys.submissionUUID]: submissionUUID, + }, + }, + }); + }); }); diff --git a/src/utils/objectUtils.js b/src/utils/objectUtils.js index 41c1196c..f078ea1d 100644 --- a/src/utils/objectUtils.js +++ b/src/utils/objectUtils.js @@ -1,5 +1,10 @@ import _ from 'lodash'; +/** + * Recursively converts keys of an object (or elements of an array) from snake_case to camelCase. + * @param {Object|Array} obj - The object or array to be transformed. + * @returns {Object|Array} - The transformed object or array with camelCase keys or elements. + */ export const camelizeKeys = (obj) => { if (Array.isArray(obj)) { return obj.map(v => camelizeKeys(v)); diff --git a/src/utils/objectUtils.test.js b/src/utils/objectUtils.test.js new file mode 100644 index 00000000..75c68f2b --- /dev/null +++ b/src/utils/objectUtils.test.js @@ -0,0 +1,73 @@ +import { camelizeKeys } from './objectUtils'; + +describe('camelizeKeys function', () => { + it('returns an empty object when an empty object is provided', () => { + const input = {}; + const output = camelizeKeys(input); + expect(output).toEqual({}); + }); + + it('converts keys from snake_case to camelCase in a simple object', () => { + const input = { + user_name: 'John', + age_of_person: 35, + address_details: { + street_address: '123 Main St', + postal_code: '12345', + }, + }; + const expectedOutput = { + userName: 'John', + ageOfPerson: 35, + addressDetails: { + streetAddress: '123 Main St', + postalCode: '12345', + }, + }; + const output = camelizeKeys(input); + expect(output).toEqual(expectedOutput); + }); + + it('converts keys from snake_case to camelCase in an array of objects', () => { + const input = [ + { + first_name: 'Alice', + last_name: 'Smith', + age_group: '30-40', + }, + { + first_name: 'Bob', + last_name: 'Johnson', + age_group: '20-30', + }, + ]; + const expectedOutput = [ + { + firstName: 'Alice', + lastName: 'Smith', + ageGroup: '30-40', + }, + { + firstName: 'Bob', + lastName: 'Johnson', + ageGroup: '20-30', + }, + ]; + const output = camelizeKeys(input); + expect(output).toEqual(expectedOutput); + }); + + it('returns the same output if non-object/array values are provided', () => { + const input = 'This is a string'; + const output = camelizeKeys(input); + expect(output).toEqual(input); + + const nullInput = null; + const nullOutput = camelizeKeys(nullInput); + expect(nullOutput).toEqual(null); + + const undefinedInput = undefined; + const undefinedOutput = camelizeKeys(undefinedInput); + expect(undefinedOutput).toEqual(undefined); + }); +}); From c68ad73f0576f2c9acf501c8d34e07d805cf266e Mon Sep 17 00:00:00 2001 From: johnvente Date: Tue, 26 Dec 2023 09:56:53 -0500 Subject: [PATCH 14/24] test: full unit test for review problem estep content hook and change it for test --- package.json | 1 + .../ReviewProblemStepActions/index.test.jsx | 14 +- .../AssessmentsTable/index.test.jsx | 34 ++-- .../components/ErrorMessage/index.test.jsx | 10 +- .../components/ResponseItem/index.test.jsx | 6 +- .../components/ResponsesList/index.test.jsx | 14 +- .../StartGradingButton/hooks.test.js | 12 +- .../StartGradingButton/index.test.jsx | 2 +- .../ReviewProblemStepsContent/hooks.js | 16 +- .../ReviewProblemStepsContent/hooks.test.js | 173 +++++++++++++++--- .../ReviewProblemStepsContent/index.test.jsx | 71 ++++++- .../ReviewProblemStepsContent/utils.test.js | 6 +- .../ReviewProblemStepsModal/index.jsx | 5 +- .../ReviewProblemStepsModal/index.test.jsx | 137 +++++++++++++- .../ReviewProblemStepsModal/utils.test.js | 18 +- src/data/redux/problem-steps/reducer.test.js | 10 +- 16 files changed, 424 insertions(+), 105 deletions(-) diff --git a/package.json b/package.json index 5ce63086..625eeaa3 100755 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@openedx/paragon": "21.11.3", "@redux-beacon/segment": "^1.1.0", "@reduxjs/toolkit": "^1.6.1", + "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.0.0", "@zip.js/zip.js": "^2.4.6", "axios": "^0.28.0", diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx index 2778ff85..dd9bcdf2 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx @@ -23,44 +23,44 @@ describe('ReviewProblemStepActions component', () => { }); describe('Should render the correct titles', () => { - it('renders the correct email title', () => { + test('renders the correct email title', () => { expect(wrapper.find('[data-testid="email-title"]').text()).toEqual( formatMessage(messages.emailTitle), ); }); - it('renders the correct submission ID title', () => { + test('renders the correct submission ID title', () => { expect(wrapper.find('[data-testid="submission-id-title"]').text()).toEqual( formatMessage(messages.submissionIdTitle), ); }); - it('renders the correct submission date title', () => { + test('renders the correct submission date title', () => { expect(wrapper.find('[data-testid="submission-date-title"]').text()).toEqual( formatMessage(messages.submissionDateTitle), ); }); - it('renders the correct grade title', () => { + test('renders the correct grade title', () => { expect(wrapper.find('[data-testid="grade-title"]').text()).toEqual( formatMessage(messages.gradeTitle), ); }); - it('renders the correct grading status title', () => { + test('renders the correct grading status title', () => { expect(wrapper.find('[data-testid="grade-status-title"]').text()).toEqual( formatMessage(messages.gradingStatus), ); }); - it('renders the correct problem steps title', () => { + test('renders the correct problem steps title', () => { expect(wrapper.find('[data-testid="problem-steps-title"]').text()).toEqual( formatMessage(messages.problemStepsTitle), ); }); }); - it('renders the correct props data', () => { + test('renders the correct props data', () => { expect(true).toBe(true); expect(wrapper.find('[data-testid="fullname-value"]').text()).toEqual('John Doe'); expect(wrapper.find('[data-testid="username-value"]').text()).toEqual('john_20'); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx index 24252f30..8b33dd89 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.test.jsx @@ -112,18 +112,18 @@ describe('AssessmentsTable component', () => { assessmentsList={mockDataTableData} />); }); - it('renders without crashing', () => { + test('renders without crashing', () => { expect(el.exists()).toBe(true); }); - it('renders the correct title', () => { + test('renders the correct title', () => { const h3Title = el.find('h3'); expect(h3Title.text()).toBe( formatMessage(messages.assessmentsTableTitle), ); }); - it('should render assessment given button and assessment received button', () => { + test('should render assessment given button and assessment received button', () => { const assessmentReceivedButton = el.find('[data-testid="assessments-received-button"]'); const assessmentGivenButton = el.find('[data-testid="assessments-given-button"]'); @@ -138,7 +138,7 @@ describe('AssessmentsTable component', () => { ); }); - it('should call the correct onClick handler when the "Received Assessments" button is clicked', () => { + test('should call the correct onClick handler when the "Received Assessments" button is clicked', () => { const assessmentReceivedButton = el.find('[data-testid="assessments-received-button"]'); assessmentReceivedButton.simulate('click'); @@ -150,7 +150,7 @@ describe('AssessmentsTable component', () => { expect(handleReceivedAssessmentsSpy).toHaveBeenCalled(); }); - it('should call the correct onClick handler when the "Given Assessments" button is clicked', () => { + test('should call the correct onClick handler when the "Given Assessments" button is clicked', () => { const assessmentGivenButton = el.find('[data-testid="assessments-given-button"]'); assessmentGivenButton.simulate('click'); @@ -181,52 +181,52 @@ describe('AssessmentsTable component', () => { columns = tableProps.columns; }); - it('ID Assessment column', () => { + test('ID Assessment column', () => { expect(columns[0]).toEqual({ Header: formatMessage(messages.idAssessmentColumnTitle), accessor: 'idAssessment', }); }); - it('Reviewer name or Learner name column', () => { + test('Reviewer name or Learner name column', () => { expect(columns[1]).toEqual({ Header: formatMessage(messages.reviewerNameColumnTitle), accessor: 'reviewerName', }); }); - it('User name column', () => { + test('User name column', () => { expect(columns[2]).toEqual({ Header: formatMessage(messages.usernameColumnTitle), accessor: 'userName', }); }); - it('Email column', () => { + test('Email column', () => { const emailColumn = tableProps.columns.find((column) => column.accessor === 'email'); expect(emailColumn.Header).toBe( formatMessage(messages.emailColumnTitle), ); }); - it('Assessment date column', () => { + test('Assessment date column', () => { const assessmentDateColumn = tableProps.columns.find((column) => column.accessor === 'assessmentDate'); expect(assessmentDateColumn.Header).toBe( formatMessage(messages.assessmentDateColumnTitle), ); }); - it('Assessment scores column', () => { + test('Assessment scores column', () => { const assessmentScoresColumn = tableProps.columns.find((column) => column.accessor === 'assessmentScores'); expect(assessmentScoresColumn.Header).toBe( formatMessage(messages.assessmentScoresColumnTitle), ); }); - it('Problem step column', () => { + test('Problem step column', () => { const problemStepColumn = tableProps.columns.find((column) => column.accessor === 'problemStep'); expect(problemStepColumn.Header).toBe( formatMessage(messages.problemStepColumnTitle), ); }); - it('Feedback column', () => { + test('Feedback column', () => { const feedbackColumn = tableProps.columns.find((column) => column.accessor === 'feedback'); expect(feedbackColumn.Header).toBe( formatMessage(messages.feedbackColumnTitle), @@ -246,12 +246,12 @@ describe('AssessmentsTable component', () => { Cell = () => assessmentScoresColumn.Cell({ value: assessmentScores }); }); - it('renders assessment scores correctly', () => { + test('renders assessment scores correctly', () => { const wrapper = shallow(); expect(wrapper.find('li')).toHaveLength(2); }); - it('renders assessment scores with correct values', () => { + test('renders assessment scores with correct values', () => { const wrapper = shallow(); expect(wrapper.find('li').at(0).text()).toBe('Ideas: Fair (1)'); expect(wrapper.find('li').at(1).text()).toBe('Content: Excellent (5)'); @@ -271,7 +271,7 @@ describe('AssessmentsTable component', () => { Cell = () => emailColumn.Cell({ value: email }); }); - it('renders Hyperlink with correct email', () => { + test('renders Hyperlink with correct email', () => { const wrapper = shallow(); expect(wrapper.find(Hyperlink).prop('children')).toBe(emailValue); }); @@ -289,7 +289,7 @@ describe('AssessmentsTable component', () => { Cell = () => problemStepColumn.Cell({ value: problemStep }); }); - it('renders Button with correct status and title', () => { + test('renders Button with correct status and title', () => { const wrapper = shallow(); expect(wrapper.find(Button).prop('children').props.status).toBe('graded'); expect(wrapper.find(Button).prop('children').props.title).toBe('Staff'); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx index 6b6e4755..12201c61 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx @@ -15,15 +15,15 @@ describe('ErrorMessage component', () => { wrapper = shallow(); }); - it('renders without crashing', () => { + test('renders without crashing', () => { expect(wrapper.exists()).toBe(true); }); - it('renders an Alert component', () => { + test('renders an Alert component', () => { expect(wrapper.find(Alert)).toHaveLength(1); }); - it('renders Alert with correct props', () => { + test('renders Alert with correct props', () => { const alertComponent = wrapper.find(Alert); expect(alertComponent.prop('variant')).toBe('danger'); @@ -31,7 +31,7 @@ describe('ErrorMessage component', () => { expect(alertComponent.prop('stacked')).toBe(true); }); - it('renders Alert.Heading with provided title', () => { + test('renders Alert.Heading with provided title', () => { const title = 'Error Title Testing'; const message = 'Error Message'; wrapper = shallow(); @@ -40,7 +40,7 @@ describe('ErrorMessage component', () => { expect(alertHeading.text()).toBe(title); }); - it('renders message within the Alert', () => { + test('renders message within the Alert', () => { const title = 'Error Title'; const message = 'Error Message Testing'; wrapper = shallow(); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.test.jsx index 25d61d6e..9a3264b8 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.test.jsx @@ -3,12 +3,12 @@ import { shallow, mount } from 'enzyme'; import ResponseItem from '.'; describe('ResponseItem component', () => { - it('renders without crashing', () => { + test('renders without crashing', () => { const wrapper = shallow(); expect(wrapper.exists()).toBe(true); }); - it('displays the title and response', () => { + test('displays the title and response', () => { const title = 'Title'; const response = 'Response Content'; const wrapper = shallow(); @@ -16,7 +16,7 @@ describe('ResponseItem component', () => { expect(wrapper.find('.collapsible-body').text()).toBe(response); }); - it('collapses when trigger is clicked twice', () => { + test('collapses when trigger is clicked twice', () => { const title = 'Title'; const response = 'Response Content'; const wrapper = mount(); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx index 4bda380c..6735b0b1 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx @@ -19,18 +19,18 @@ describe('ResponsesList component', () => { el = shallow(); }); - it('renders without crashing', () => { + test('renders without crashing', () => { expect(el.exists()).toBe(true); }); - it('renders the correct title', () => { + test('renders the correct title', () => { const h3Title = el.find('h3'); expect(h3Title.text()).toBe( formatMessage(messages.responsesDetailListTitle), ); }); - it('list ordered button and list-grid-button must be rendered', () => { + test('list ordered button and list-grid-button must be rendered', () => { const formatListOrderedButton = el.find( '[data-testid="list-ordered-button"]', ); @@ -39,12 +39,12 @@ describe('ResponsesList component', () => { expect(formatListGridButton.exists()).toBe(true); }); - it('When has isLoaded prop should show "Show Rubric" button" ', () => { + test('When has isLoaded prop should show "Show Rubric" button" ', () => { const showRubricButton = el.find('[data-testid="show-rubric-button"]'); expect(showRubricButton.exists()).toBe(true); }); - it('renders the list of responses', () => { + test('renders the list of responses', () => { const responsesList = [ { id: 'adb123', @@ -68,7 +68,7 @@ describe('ResponsesList component', () => { }); }); - it('should toggle list format when "list-ordered-button" is clicked', () => { + test('should toggle list format when "list-ordered-button" is clicked', () => { const listGridButton = el.find('[data-testid="list-grid-button"]'); listGridButton.simulate('click'); @@ -83,7 +83,7 @@ describe('ResponsesList component', () => { expect(listOrderedButtonAfterListOrderedClick.prop('className')).toBe('mb-2 mb-sm-0 list__active'); }); - it('should toggle list format when "list-grid-button" is clicked', () => { + test('should toggle list format when "list-grid-button" is clicked', () => { const listOrderedButton = el.find('[data-testid="list-ordered-button"]'); listOrderedButton.simulate('click'); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/hooks.test.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/hooks.test.js index 89ab6e6b..0fa8fa60 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/hooks.test.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/hooks.test.js @@ -92,25 +92,25 @@ describe('Start Grading Button hooks', () => { selectors.grading.selected.gradingStatus.mockReturnValue(status); hook = hooks.buttonArgs({ ...props, gradingStatus: status }); }); - it('loads configured iconAfter', () => { + test('loads configured iconAfter', () => { expect(hook.iconAfter).toEqual(hooks.buttonConfig[status].iconAfter); }); - it('loads and formats label from config', () => { + test('loads and formats label from config', () => { expect(hook.children).toEqual(formatMessage(hooks.buttonConfig[status].label)); }); describe('onClick', () => { if (status === gradingStatuses.inProgress) { - it('shows the confirm stop-grading modal', () => { + test('shows the confirm stop-grading modal', () => { hook.onClick(); expect(props.stopGradingState.setShow).toHaveBeenCalledWith(true); }); } else if (status === gradingStatuses.graded) { - it('shows the confirm stop-grading modal', () => { + test('shows the confirm stop-grading modal', () => { hook.onClick(); expect(props.overrideGradeState.setShow).toHaveBeenCalledWith(true); }); } else { - it('dispatches the startGrading thunkAction', () => { + test('dispatches the startGrading thunkAction', () => { hook.onClick(); expect(props.dispatch).toHaveBeenCalledWith(thunkActions.grading.startGrading()); }); @@ -207,7 +207,7 @@ describe('Start Grading Button hooks', () => { }; }); describe('behavior', () => { - it('initializes showConfirmStopGrading and showConfirmOverrideGrade to false', () => { + test('initializes showConfirmStopGrading and showConfirmOverrideGrade to false', () => { expect(hooks.state.showConfirmStopGrading).toHaveBeenCalledWith(false); expect(hooks.state.showConfirmOverrideGrade).toHaveBeenCalledWith(false); }); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/index.test.jsx index 5da14a41..2d69b1b1 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/index.test.jsx @@ -28,7 +28,7 @@ describe('StartGradingButton component', () => { stopGradingArgs: { props: 'hooks.stopGradingArgs' }, }; describe('behavior', () => { - it('initializes buttonHooks with dispatch and intl fields', () => { + test('initializes buttonHooks with dispatch and intl fields', () => { hooks.buttonHooks.mockReturnValueOnce(buttonHooks); el = shallow(); expect(hooks.buttonHooks).toHaveBeenCalledWith({ dispatch, intl }); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js index 5afb5811..25ca053f 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js @@ -8,8 +8,8 @@ export const useFeedbackList = (submissionUUID) => { const [feedbackListError, setFeedbackListError] = useState(null); const [feedbackListType, setFeedbackListType] = useState('received'); - useEffect(() => { - const getFeedbackList = async () => { + const getFeedbackListApi = async () => { + if (submissionUUID) { setIsLoadingFeedbackList(true); try { const data = await api.getFeedbackList(submissionUUID, feedbackListType); @@ -17,12 +17,17 @@ export const useFeedbackList = (submissionUUID) => { const formatData = assessmentTableFormat(assessments); setFeedbackList(formatData); } catch (error) { - setFeedbackListError('Error'); + setFeedbackListError(error.message); } finally { setIsLoadingFeedbackList(false); } - }; - getFeedbackList(); + } + }; + + useEffect(() => { + /* istanbul ignore next */ + getFeedbackListApi(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [feedbackListType, submissionUUID]); return { @@ -31,5 +36,6 @@ export const useFeedbackList = (submissionUUID) => { feedbackListError, feedbackListType, setFeedbackListType, + getFeedbackListApi, }; }; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.test.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.test.js index 0d2fe942..5b4746bd 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.test.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.test.js @@ -1,34 +1,147 @@ -import { useFeedbackList } from './hooks'; // Import your hook - -jest.mock('./hooks'); - -describe('useFeedbackList hook', () => { - it('Should returns correctly data', async () => { - const hookMock = { - isLoadingFeedbackList: true, - feedbackList: [], - feedbackListError: null, - feedbackListType: 'received', - setFeedbackListType: jest.fn(), - }; - useFeedbackList.mockReturnValue(hookMock); - const hookReturn = useFeedbackList('sampleUUID'); - expect(hookMock).toBe(hookReturn); +import { renderHook, act } from '@testing-library/react-hooks'; +import api from 'data/services/lms/api'; +import { useFeedbackList } from './hooks'; + +jest.mock('data/services/lms/api', () => ({ + getFeedbackList: jest.fn(), +})); + +describe('ReviewProblemStepsContent hooks', () => { + beforeEach(() => { + jest.clearAllMocks(); }); + describe('useFeedbackList', () => { + const mockAssessments = [ + { + idAssessment: 1, + assesmentDate: '2024-01-01', + scorerEmail: 'email@example.com', + scorerName: 'John Doe', + scorerUsername: 'johndoe123', + feedback: 'Great work!', + problemStep: 'Step 1', + assesmentScores: [ + { + criterionName: 'Criterion 1', + scoreEarned: 8, + scoreType: 'Good', + }, + ], + }, + ]; + + const expectedFormattedAssessments = [ + { + idAssessment: 1, + reviewerName: 'John Doe', + userName: 'johndoe123', + email: 'email@example.com', + assessmentDate: '2024-01-01', + assessmentScores: [ + { + id: expect.any(String), + type: 'Criterion 1', + quality: 'Good', + rate: 8, + }, + ], + feedback: 'Great work!', + problemStep: 'Step 1', + }, + ]; + + test('initial state', () => { + const { result } = renderHook(() => useFeedbackList('some-uuid')); + + expect(result.current.isLoadingFeedbackList).toBe(false); + expect(result.current.feedbackList).toEqual([]); + expect(result.current.feedbackListError).toBeNull(); + expect(result.current.feedbackListType).toBe('received'); + }); + + test('should change feedbackListType', async () => { + const mockResponse = { assessments: mockAssessments }; + api.getFeedbackList.mockResolvedValue(mockResponse); + + const { result, waitForNextUpdate } = renderHook(() => useFeedbackList('some-uuid')); + + act(() => result.current.setFeedbackListType('given')); + + await act(async () => { + result.current.getFeedbackListApi(); + await waitForNextUpdate(); + }); + + expect(api.getFeedbackList).toHaveBeenCalledWith('some-uuid', 'given'); + expect(result.current.isLoadingFeedbackList).toBe(false); + expect(result.current.feedbackList).toEqual(expectedFormattedAssessments); + expect(result.current.feedbackListError).toBeNull(); + }); + + test('successful API call', async () => { + const mockResponse = { assessments: mockAssessments }; + api.getFeedbackList.mockResolvedValue(mockResponse); + + const { result, waitForNextUpdate } = renderHook(() => useFeedbackList('some-uuid')); + + act(() => { + result.current.getFeedbackListApi(); + }); + + expect(api.getFeedbackList).toHaveBeenCalledWith('some-uuid', 'received'); + expect(result.current.isLoadingFeedbackList).toBe(true); + expect(result.current.feedbackList).toEqual([]); + expect(result.current.feedbackListError).toBeNull(); + + await act(async () => { + result.current.getFeedbackListApi(); + await waitForNextUpdate(); + }); + + expect(api.getFeedbackList).toHaveBeenCalledWith('some-uuid', 'received'); + expect(result.current.isLoadingFeedbackList).toBe(false); + expect(result.current.feedbackList).toEqual(expectedFormattedAssessments); + expect(result.current.feedbackListError).toBeNull(); + }); + + test('fail API call', async () => { + const mockError = new Error('Error fetching data'); + api.getFeedbackList.mockRejectedValue(mockError); + + const { result, waitForNextUpdate } = renderHook(() => useFeedbackList('some-uuid')); + + act(() => { + result.current.getFeedbackListApi(); + }); + + expect(api.getFeedbackList).toHaveBeenCalledWith('some-uuid', 'received'); + expect(result.current.isLoadingFeedbackList).toBe(true); + expect(result.current.feedbackListError).toBeNull(); + + await act(async () => { + result.current.getFeedbackListApi(); + await waitForNextUpdate(); + }); + + expect(api.getFeedbackList).toHaveBeenCalledWith('some-uuid', 'received'); + expect(result.current.isLoadingFeedbackList).toBe(false); + expect(result.current.feedbackListError).toBe('Error fetching data'); + }); + + test('does not make API call if submissionUUID is null', async () => { + const { result, waitFor } = renderHook(() => useFeedbackList(null)); + + act(() => { + result.current.getFeedbackListApi(); + }); + + await waitFor(() => { + expect(result.current.isLoadingFeedbackList).toBe(false); + }, { timeout: 500 }); - it('Should call setFeedbackListType correctly', async () => { - const hookMock = { - isLoadingFeedbackList: true, - feedbackList: [], - feedbackListError: null, - feedbackListType: 'received', - setFeedbackListType: jest.fn(), - }; - - useFeedbackList.mockReturnValue(hookMock); - const hookReturn = useFeedbackList('sampleUUID'); - hookReturn.setFeedbackListType(); - expect(hookMock).toBe(hookReturn); - expect(hookMock.setFeedbackListType).toBeCalled(); + expect(api.getFeedbackList).not.toHaveBeenCalled(); + expect(result.current.feedbackList).toEqual([]); + expect(result.current.feedbackListError).toBeNull(); + }); }); }); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.test.jsx index d5ab91c7..e395b9a6 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/index.test.jsx @@ -5,6 +5,8 @@ import { ReviewProblemStepsContent, mapStateToProps } from '.'; import ResponsesList from './components/ResponsesList'; import AssessmentsTable from './components/AssessmentsTable'; import ErrorMessage from './components/ErrorMessage'; +import * as hooks from './hooks'; +import messages from './messages'; describe('ReviewProblemStepsContent component', () => { const defaultResponses = { text: [], files: [] }; @@ -20,36 +22,93 @@ describe('ReviewProblemStepsContent component', () => { return shallow(); }; - it('renders without crashing', () => { + test('renders without crashing', () => { const wrapper = createWrapper(); expect(wrapper.exists()).toBe(true); }); - it('renders ResponsesList component', () => { + test('renders ResponsesList component', () => { const wrapper = createWrapper(); expect(wrapper.find(ResponsesList).length).toBe(1); }); - it('renders AssessmentsTable component', () => { + test('renders AssessmentsTable component', () => { const wrapper = createWrapper(); expect(wrapper.find(AssessmentsTable).length).toBe(1); }); - it('passes toggleShowRubric prop', () => { + test('passes toggleShowRubric prop', () => { const toggleShowRubric = jest.fn(); const wrapper = createWrapper({ toggleShowRubric }); const responsesList = wrapper.find(ResponsesList); expect(responsesList.prop('toggleShowRubric')).toBe(toggleShowRubric); }); - it('renders ErrorMessage when hasDetailSubmissionError is true', () => { + test('renders ErrorMessage when hasDetailSubmissionError is true', () => { const wrapper = createWrapper({ hasDetailSubmissionError: true }); expect(wrapper.find(ErrorMessage).exists()).toBe(true); }); + + test('renders ErrorMessage when feedbackListError is true and isLoadingFeedbackList is false', () => { + const mockedValues = { + isLoadingFeedbackList: false, + feedbackList: [], + feedbackListError: true, + setFeedbackListType: jest.fn(), + }; + + jest.spyOn(hooks, 'useFeedbackList').mockReturnValue(mockedValues); + + const wrapper = createWrapper(); + const errorMessage = wrapper.find(ErrorMessage); + expect(errorMessage.exists()).toBe(true); + expect(errorMessage.prop('title')).toBe(formatMessage(messages.feedbackListTitleError)); + expect(errorMessage.prop('message')).toBe(formatMessage(messages.feedbackListMessageError)); + }); + + test('renders AssessmentsTable when feedbackListError is false or isLoadingFeedbackList is true', () => { + const mockedValues = { + isLoadingFeedbackList: false, + feedbackList: [], + feedbackListError: false, + setFeedbackListType: jest.fn(), + }; + + jest.spyOn(hooks, 'useFeedbackList').mockReturnValue(mockedValues); + + const wrapper = createWrapper(); + const errorMessage = wrapper.find(ErrorMessage); + const assessmentsTable = wrapper.find(AssessmentsTable); + + expect(errorMessage.exists()).toBe(false); + expect(assessmentsTable.exists()).toBe(true); + }); + + test('should call setFeedbackListType with correct arguments when assessments are clicked', () => { + const mockedValues = { + isLoadingFeedbackList: false, + feedbackList: [], + feedbackListError: false, + setFeedbackListType: jest.fn(), + }; + + jest.spyOn(hooks, 'useFeedbackList').mockReturnValue(mockedValues); + + const wrapper = createWrapper(); + const assessmentsTable = wrapper.find(AssessmentsTable); + + // Simulate click on received assessment + assessmentsTable.prop('onClickReceivedAssessment')(); + // Simulate click on given assessment + assessmentsTable.prop('onClickGivenAssessment')(); + + expect(mockedValues.setFeedbackListType).toHaveBeenCalledWith('received'); + expect(mockedValues.setFeedbackListType).toHaveBeenCalledWith('given'); + }); }); describe('mapStateToProps', () => { - it('maps state to props correctly', () => { + test('maps state to props correctly', () => { const initialState = { app: { showRubric: true, diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.test.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.test.js index 6398ddcc..e7309293 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.test.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.test.js @@ -1,7 +1,7 @@ import { assessmentTableFormat, responsesListFormat } from './utils'; describe('assessmentTableFormat', () => { - it('formats assessment data correctly', () => { + test('formats assessment data correctly', () => { const inputAssessmentData = [ { idAssessment: 1, @@ -44,7 +44,7 @@ describe('assessmentTableFormat', () => { }); describe('responsesListFormat', () => { - it('formats responses data correctly', () => { + test('formats responses data correctly', () => { const inputResponsesData = [ 'Response 1', 'Response 2', @@ -58,7 +58,7 @@ describe('responsesListFormat', () => { ]); }); - it('returns an empty array for invalid input', () => { + test('returns an empty array for invalid input', () => { const invalidInput = 'Invalid'; const formattedResponsesData = responsesListFormat(invalidInput); diff --git a/src/containers/ReviewProblemStepsModal/index.jsx b/src/containers/ReviewProblemStepsModal/index.jsx index b647c3a5..13c13abb 100644 --- a/src/containers/ReviewProblemStepsModal/index.jsx +++ b/src/containers/ReviewProblemStepsModal/index.jsx @@ -53,11 +53,12 @@ export const ReviewProblemStepsModal = () => { submissionUUID={submissionUUID} hasDetailSubmissionError={hasDetailSubmissionError} responses={response} + data-testid="review-step-problems-content" /> )} {/* even if the modal is closed, in case we want to add transitions later */} - {isLoading && } - + {isLoading && } + ); }; diff --git a/src/containers/ReviewProblemStepsModal/index.test.jsx b/src/containers/ReviewProblemStepsModal/index.test.jsx index ff7f85cb..64b51924 100644 --- a/src/containers/ReviewProblemStepsModal/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/index.test.jsx @@ -1,11 +1,12 @@ import { useSelector } from 'react-redux'; - +import { shallow } from 'enzyme'; import { keyStore } from 'utils'; import { MockUseState, formatMessage } from 'testUtils'; import { selectors, thunkActions } from 'data/redux'; import { RequestKeys } from 'data/constants/requests'; import * as hooks from './hooks'; +import { ReviewProblemStepsModal } from '.'; const state = new MockUseState(hooks); @@ -36,6 +37,9 @@ jest.mock('data/redux', () => ({ problemSteps: { setOpenReviewModal: jest.fn(), }, + grading: { + setCriterionOption: jest.fn(), + }, }, thunkActions: { app: { @@ -44,6 +48,7 @@ jest.mock('data/redux', () => ({ }, grading: { cancelGrading: jest.fn(), + setCriterionOption: jest.fn(), }, }, })); @@ -54,6 +59,10 @@ const hookKeys = keyStore(hooks); const testState = { my: 'test-state' }; const intl = { formatMessage }; const dispatch = jest.fn(); + +jest.mock('./components/ReviewProblemStepsContent'); +jest.mock('containers/DemoWarning'); + describe('ReviewProblemStepsModal hooks', () => { beforeEach(() => { jest.clearAllMocks(); @@ -111,12 +120,12 @@ describe('ReviewProblemStepsModal hooks', () => { describe('rendererHooks - returned object:', () => { let hook; describe('onClose', () => { - it('sets showConfirmCloseReviewGrade to true if hasGradingProgress', () => { + test('sets showConfirmCloseReviewGrade to true if hasGradingProgress', () => { hook = loadHook({ hasGradingProgress: true }); hook.onClose(); expect(state.setState.showConfirmCloseReviewGrade).toHaveBeenCalledWith(true); }); - it('cancels review if there is no grading progress', () => { + test('cancels review if there is no grading progress', () => { hook = loadHook({}); hook.onClose(); expect(dispatch).toHaveBeenCalledWith(thunkActions.app.cancelReview()); @@ -153,4 +162,126 @@ describe('ReviewProblemStepsModal hooks', () => { }); }); }); + + describe('', () => { + const generateMockedValues = ({ + stateMock, submissionUUID, response, errorStatus, isModalOpen, isLoading, + }) => ({ + state: stateMock, + submissionUUID, + response, + errorStatus, + submissions: {}, + currentSubmission: { submissionUUID, response }, + isLoading, + isModalOpen, + }); + + const renderHooks = ({ + stateMock, submissionUUID, response, errorStatus, isModalOpen, isLoading, + }) => { + jest.spyOn(hooks, 'rendererHooks').mockReturnValue( + generateMockedValues({ + stateMock, + submissionUUID, + response, + errorStatus, + isModalOpen, + isLoading, + }), + ); + }; + + const stateMock = { + showConfirmCloseReviewGrade: jest.fn(), + }; + + const mockedSubmissionUUID = 'mockedUUID'; + const mockedResponse = []; + const mockedErrorStatus = undefined; + + test('renders ReviewProblemStepsContent when isModalOpen is true and has submissionUUID', () => { + renderHooks({ + submissionUUID: mockedSubmissionUUID, + response: mockedResponse, + errorStatus: mockedErrorStatus, + submissions: { [mockedSubmissionUUID]: { score: null } }, + currentSubmission: { submissionUUID: mockedSubmissionUUID, response: mockedResponse }, + isModalOpen: true, + stateMock, + }); + + const wrapper = shallow( + , + ); + + expect(wrapper.find('[data-testid="review-step-problems-content"]').exists()).toBe(true); + expect(wrapper.find('[data-testid="close-review-modal"]').exists()).toBe(true); + }); + + test('should not render ReviewProblemStepsContent when isModalOpen is false and has submissionUUID', () => { + renderHooks({ + submissionUUID: mockedSubmissionUUID, + response: mockedResponse, + errorStatus: mockedErrorStatus, + submissions: { [mockedSubmissionUUID]: { score: null } }, + currentSubmission: { submissionUUID: mockedSubmissionUUID, response: mockedResponse }, + isModalOpen: false, + stateMock, + }); + + const wrapper = shallow( + , + ); + + expect(wrapper.find('[data-testid="review-step-problems-content"]').exists()).toBe(false); + expect(wrapper.find('[data-testid="close-review-modal"]').exists()).toBe(true); + }); + + test('should render LoadingMessage when isLoading is true', () => { + renderHooks({ + submissionUUID: mockedSubmissionUUID, + response: mockedResponse, + errorStatus: mockedErrorStatus, + submissions: { [mockedSubmissionUUID]: { score: null } }, + currentSubmission: { submissionUUID: mockedSubmissionUUID, response: mockedResponse }, + isModalOpen: false, + stateMock, + isLoading: true, + }); + + const wrapper = shallow( + , + ); + + expect(wrapper.find('[data-testid="loading-message"]').exists()).toBe(true); + }); + + test('should not render LoadingMessage when isLoading is false', () => { + renderHooks({ + submissionUUID: mockedSubmissionUUID, + response: mockedResponse, + errorStatus: mockedErrorStatus, + submissions: { [mockedSubmissionUUID]: { score: null } }, + currentSubmission: { submissionUUID: mockedSubmissionUUID, response: mockedResponse }, + isModalOpen: false, + stateMock, + isLoading: false, + }); + + const wrapper = shallow( + , + ); + + expect(wrapper.find('[data-testid="loading-message"]').exists()).toBe(false); + }); + }); }); diff --git a/src/containers/ReviewProblemStepsModal/utils.test.js b/src/containers/ReviewProblemStepsModal/utils.test.js index 3e090443..a361afe5 100644 --- a/src/containers/ReviewProblemStepsModal/utils.test.js +++ b/src/containers/ReviewProblemStepsModal/utils.test.js @@ -3,44 +3,44 @@ import { } from './utils'; describe('formatDate util', () => { - it('formats date string into a localized string', () => { + test('formats date string into a localized string', () => { const date = '2023-11-17T08:30:00Z'; const formattedDate = formatDate(date); expect(formattedDate).toBe(new Date(date).toLocaleString()); }); - it('handles empty date string', () => { + test('handles empty date string', () => { const formattedDate = formatDate(); expect(formattedDate).toBe(new Date('').toLocaleString()); }); }); describe('formatGrade util', () => { - it('formats score object into a grade string', () => { + test('formats score object into a grade string', () => { const score = { pointsEarned: 85, pointsPossible: 100 }; const formattedGrade = formatGrade(score); expect(formattedGrade).toBe('85/100'); }); - it('handles null score object', () => { + test('handles null score object', () => { const formattedGrade = formatGrade(null); expect(formattedGrade).toBe('-'); }); }); describe('capitalizeFirstLetter util', () => { - it('capitalizes the first letter of a string', () => { + test('capitalizes the first letter of a string', () => { const str = 'test'; const capitalizedStr = capitalizeFirstLetter(str); expect(capitalizedStr).toBe('Test'); }); - it('handles empty string', () => { + test('handles empty string', () => { const capitalizedStr = capitalizeFirstLetter(); expect(capitalizedStr).toBe(''); }); - it('handles already capitalized util', () => { + test('handles already capitalized util', () => { const str = 'Test'; const capitalizedStr = capitalizeFirstLetter(str); expect(capitalizedStr).toBe('Test'); @@ -48,7 +48,7 @@ describe('capitalizeFirstLetter util', () => { }); describe('transformObjectToDetail util', () => { - it('transforms object details as expected', () => { + test('transforms object details as expected', () => { const inputObject = { fullname: 'John Doe', username: 'johndoe123', @@ -73,7 +73,7 @@ describe('transformObjectToDetail util', () => { expect(transformedObject).toEqual(expectedOutput); }); - it('handles missing or empty properties in the input object', () => { + test('handles missing or empty properties in the input object', () => { const inputObject = { fullname: 'Jane Doe', }; diff --git a/src/data/redux/problem-steps/reducer.test.js b/src/data/redux/problem-steps/reducer.test.js index 900409cb..7d80154d 100644 --- a/src/data/redux/problem-steps/reducer.test.js +++ b/src/data/redux/problem-steps/reducer.test.js @@ -14,7 +14,6 @@ describe('problemSteps reducer', () => { }); it('should handle setOpenReviewModal with false correctly', () => { - // If you want to test setting reviewModalOpen to false const stateWithOpenModal = { ...initialState, reviewModalOpen: true }; const newState = reducer(stateWithOpenModal, actions.setOpenReviewModal(false)); @@ -22,4 +21,13 @@ describe('problemSteps reducer', () => { expect(newState.reviewModalOpen).toEqual(false); expect(newState.someOtherProperty).toEqual(stateWithOpenModal.someOtherProperty); }); + + it('should handle setSelectedSubmissionId correctly', () => { + const newSubmissionId = 'newSubmissionId'; + + const newState = reducer(initialState, actions.setSelectedSubmissionId(newSubmissionId)); + + expect(newState.selectedSubmissionId).toEqual(newSubmissionId); + expect(newState.reviewModalOpen).toEqual(initialState.reviewModalOpen); + }); }); From 3b4d83e573eb512c0fcceb4fa13898b99841eb67 Mon Sep 17 00:00:00 2001 From: johnvente Date: Tue, 26 Dec 2023 17:21:06 -0500 Subject: [PATCH 15/24] fix: addresing pr comments --- .../components/OverrideGradeConfirmModal.jsx | 1 - .../components/CloseReviewConfirmModal.jsx | 1 - .../ReviewProblemStepsContent.jsx | 3 --- .../StopGradingConfirmModal.test.jsx.snap | 2 +- .../ReviewProblemStepsContent/components/messages.js | 2 +- .../components/ReviewProblemStepsContent/hooks.js | 11 +++++++++++ .../components/ReviewProblemStepsContent/utils.js | 8 ++++---- src/containers/ReviewProblemStepsModal/index.jsx | 3 --- 8 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/containers/ReviewActions/components/OverrideGradeConfirmModal.jsx b/src/containers/ReviewActions/components/OverrideGradeConfirmModal.jsx index 27cbd527..e9bf6386 100644 --- a/src/containers/ReviewActions/components/OverrideGradeConfirmModal.jsx +++ b/src/containers/ReviewActions/components/OverrideGradeConfirmModal.jsx @@ -26,7 +26,6 @@ OverrideGradeConfirmModal.propTypes = { isOpen: PropTypes.bool.isRequired, onCancel: PropTypes.func.isRequired, onConfirm: PropTypes.func.isRequired, - // injected intl: intlShape.isRequired, }; diff --git a/src/containers/ReviewModal/components/CloseReviewConfirmModal.jsx b/src/containers/ReviewModal/components/CloseReviewConfirmModal.jsx index 4e5cb069..ac0604dc 100644 --- a/src/containers/ReviewModal/components/CloseReviewConfirmModal.jsx +++ b/src/containers/ReviewModal/components/CloseReviewConfirmModal.jsx @@ -26,7 +26,6 @@ CloseReviewConfirmModal.propTypes = { isOpen: PropTypes.bool.isRequired, onCancel: PropTypes.func.isRequired, onConfirm: PropTypes.func.isRequired, - // injected intl: intlShape.isRequired, }; diff --git a/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.jsx b/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.jsx index b50f0175..6bfcffef 100644 --- a/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.jsx +++ b/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.jsx @@ -11,9 +11,6 @@ import ResponseDisplay from 'containers/ResponseDisplay'; import Rubric from 'containers/Rubric'; import ReviewErrors from 'containers/ReviewModal/ReviewErrors'; -/** - * - */ export const ReviewProblemStepsContent = ({ isFailed, isLoaded, showRubric }) => (isLoaded || isFailed) && (
diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/__snapshots__/StopGradingConfirmModal.test.jsx.snap b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/__snapshots__/StopGradingConfirmModal.test.jsx.snap index 4ff94bfa..a384579c 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/__snapshots__/StopGradingConfirmModal.test.jsx.snap +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/__snapshots__/StopGradingConfirmModal.test.jsx.snap @@ -32,6 +32,6 @@ exports[`StopGradingConfirmModal snapshot: open, isOverride 1`] = ` isOpen={false} onCancel={[MockFunction this.props.onCancel]} onConfirm={[MockFunction this.props.onConfirm]} - title="Are you sure you want to stop grade override?" + title="Are you sure you want to stop the grade override?" /> `; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/messages.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/messages.js index d6e958d8..4fde7c8d 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/messages.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/messages.js @@ -33,7 +33,7 @@ const messages = defineMessages({ }, confirmStopOverrideTitle: { id: 'ora-grading.ReviewActions.StopGradingConfirmModal.override.title', - defaultMessage: 'Are you sure you want to stop grade override?', + defaultMessage: 'Are you sure you want to stop the grade override?', description: 'ORA stop overriding grade confirm modal title', }, confirmStopGradingTitle: { diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js index 25ca053f..1133067e 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js @@ -2,12 +2,23 @@ import { useState, useEffect } from 'react'; import api from 'data/services/lms/api'; import { assessmentTableFormat } from './utils'; +/** + * Custom React Hook to manage feedback list data. + * + * @param {string} submissionUUID - The UUID of the submission. + * @returns {object} An object containing feedback list data and functions to manage it. + */ export const useFeedbackList = (submissionUUID) => { const [isLoadingFeedbackList, setIsLoadingFeedbackList] = useState(false); const [feedbackList, setFeedbackList] = useState([]); const [feedbackListError, setFeedbackListError] = useState(null); const [feedbackListType, setFeedbackListType] = useState('received'); + /** + * Fetches feedback list data from the API based on submission UUID and type. + * + * @returns {Promise} A Promise representing the asynchronous API call. + */ const getFeedbackListApi = async () => { if (submissionUUID) { setIsLoadingFeedbackList(true); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js index 2f81e4d5..7d0ea58a 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js @@ -25,15 +25,15 @@ export const assessmentTableFormat = (arr) => arr.map(({ /** * Formats an array of responses into a specific structure. - * @param {Array} [arr=[]] - The array containing responses data. + * @param {Array} [responses=[]] - The array containing responses data. * @returns {Array} - Returns an array of formatted responses. */ -export const responsesListFormat = (arr = []) => { - if (!Array.isArray(arr)) { +export const responsesListFormat = (responses = []) => { + if (!Array.isArray(responses)) { return []; } - return arr.map((response, index) => ({ + return responses.map((response, index) => ({ id: uuidv4(), title: `Prompt ${index + 1}`, response, diff --git a/src/containers/ReviewProblemStepsModal/index.jsx b/src/containers/ReviewProblemStepsModal/index.jsx index 13c13abb..9f3a6d04 100644 --- a/src/containers/ReviewProblemStepsModal/index.jsx +++ b/src/containers/ReviewProblemStepsModal/index.jsx @@ -14,9 +14,6 @@ import * as hooks from './hooks'; import './ReviewProblemStepsModal.scss'; import { transformObjectToDetail } from './utils'; -/** - * - */ export const ReviewProblemStepsModal = () => { const dispatch = useDispatch(); const { From 6d7ea83d89a76cce2355bcb116b9dd22c549925a Mon Sep 17 00:00:00 2001 From: johnvente Date: Tue, 30 Jan 2024 15:44:15 -0500 Subject: [PATCH 16/24] fix: jest problems --- jest.config.js | 4 ++++ package.json | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/jest.config.js b/jest.config.js index 2cfe0b7d..f18f3788 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,6 +13,10 @@ module.exports = createConfig('jest', { 'src/data/services/lms/fakeData', // don't unit test mock data 'src/test', // don't unit test integration test utils ], + moduleNameMapper: { + '^@openedx/paragon$': '/mockParagon.js', + '^@openedx/paragon/(.*)$': '/mockParagon.js', + }, testTimeout: 120000, testEnvironment: 'jsdom', }); diff --git a/package.json b/package.json index 625eeaa3..19ae639d 100755 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.2.0", - "@openedx/paragon": "21.11.3", + "@openedx/paragon": "^22.0.0", "@redux-beacon/segment": "^1.1.0", "@reduxjs/toolkit": "^1.6.1", "@testing-library/react-hooks": "^8.0.1", @@ -78,7 +78,7 @@ "@edx/react-unit-test-utils": "2.0.0", "@edx/reactifex": "^2.1.1", "@openedx/frontend-build": "13.0.28", - "@testing-library/jest-dom": "^6.0.0", + "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "12.1.5", "axios-mock-adapter": "^1.20.0", "fetch-mock": "^9.11.0", From 772eb258c6e791b4630dd6de4f021f1a5350c499 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Thu, 22 Feb 2024 14:35:36 -0500 Subject: [PATCH 17/24] refactor: use individual endpoint to get assessments feedback --- src/containers/ListView/messages.js | 2 +- .../components/AssessmentsTable/index.jsx | 2 +- .../components/AssessmentsTable/messages.js | 2 +- .../ReviewProblemStepsContent/hooks.js | 7 +++++- .../ReviewProblemStepsContent/utils.js | 10 ++++---- src/data/services/lms/api.js | 23 ++++++++++++++----- src/data/services/lms/constants.js | 1 - src/data/services/lms/urls.js | 6 +++-- 8 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/containers/ListView/messages.js b/src/containers/ListView/messages.js index f4c3d2ba..793f7d0d 100644 --- a/src/containers/ListView/messages.js +++ b/src/containers/ListView/messages.js @@ -93,7 +93,7 @@ const messages = defineMessages({ }, problemSteps: { id: 'ora-grading.ListView.problemSteps', - defaultMessage: 'Problem Steps', + defaultMessage: 'Problem steps', description: 'problem steps column text for submission response list', }, problemStepsTraining: { diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx index 1bf2cec5..4e4b1443 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/index.jsx @@ -91,7 +91,7 @@ export const AssessmentsTable = ({ columns={[ { Header: intl.formatMessage(messages.idAssessmentColumnTitle), - accessor: 'idAssessment', + accessor: 'assessmentId', }, { Header: isReceivedAssessmentSelected diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/messages.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/messages.js index 0a98d44e..cfcfc742 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/messages.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/AssessmentsTable/messages.js @@ -18,7 +18,7 @@ const messages = defineMessages({ }, idAssessmentColumnTitle: { id: 'ora-grading.ReviewProblemStepsContent.idAssessmentColumnTitle', - defaultMessage: 'ID Assessment', + defaultMessage: 'ID assessment', description: 'ORA Grading ID Assessment column title', }, reviewerNameColumnTitle: { diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js index 1133067e..b4a515cd 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/hooks.js @@ -21,9 +21,14 @@ export const useFeedbackList = (submissionUUID) => { */ const getFeedbackListApi = async () => { if (submissionUUID) { + const feedbackListTypesResponses = { + received: api.getFeedbackFromList(submissionUUID), + given: api.getFeedbackToList(submissionUUID), + }; setIsLoadingFeedbackList(true); try { - const data = await api.getFeedbackList(submissionUUID, feedbackListType); + const response = feedbackListTypesResponses[feedbackListType] || Promise.resolve({ assessments: [] }); + const data = await response; const { assessments } = data; const formatData = assessmentTableFormat(assessments); setFeedbackList(formatData); diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js index 7d0ea58a..c222b75f 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/utils.js @@ -6,18 +6,18 @@ import { v4 as uuidv4 } from 'uuid'; * @returns {Array} - Returns an array of formatted assessment data. */ export const assessmentTableFormat = (arr) => arr.map(({ - idAssessment, assesmentDate, scorerEmail, scorerName, scorerUsername, feedback, problemStep, assesmentScores, + assessmentId, assessmentDate, scorerEmail, scorerName, scorerUsername, feedback, problemStep, assessmentScores, }) => { - const newAssesmentScores = assesmentScores.map(({ criterionName, scoreEarned, scoreType }) => ({ + const newAssessmentScores = assessmentScores.map(({ criterionName, scoreEarned, scoreType }) => ({ id: uuidv4(), type: criterionName, quality: scoreType, rate: scoreEarned, })); return { - idAssessment, + assessmentId, reviewerName: scorerName, userName: scorerUsername, email: scorerEmail, - assessmentDate: assesmentDate, - assessmentScores: newAssesmentScores, + assessmentDate, + assessmentScores: newAssessmentScores, feedback, problemStep, }; diff --git a/src/data/services/lms/api.js b/src/data/services/lms/api.js index f8571b91..2eafea69 100644 --- a/src/data/services/lms/api.js +++ b/src/data/services/lms/api.js @@ -132,14 +132,24 @@ const updateGrade = (submissionUUID, gradeData) => post( ).then(response => response.data); /* - * get('api/assessments/feedback', { submissionUUID, assessmentType }) - * @param {object} feedbackData - full grading feedback of submission data + * get('api/assessments/feedback/from', { submissionUUID }) + * @param {object} feedbackData - full grading feedback received of submission data */ -const getFeedbackList = (submissionUUID, assessmentType) => get( - stringifyUrl(urls.getFeedbackSubmissionsUrl(), { +const getFeedbackFromList = (submissionUUID) => get( + stringifyUrl(urls.assessmentsFeedbackFromUrl(), { + [paramKeys.oraLocation]: locationId(), + [paramKeys.submissionUUID]: submissionUUID, + }), +).then(response => camelizeKeys(response.data)); + +/* + * get('api/assessments/feedback/to', { submissionUUID }) + * @param {object} feedbackData - full grading feedback given of submission data + */ +const getFeedbackToList = (submissionUUID) => get( + stringifyUrl(urls.assessmentFeedbackToUrl(), { [paramKeys.oraLocation]: locationId(), [paramKeys.submissionUUID]: submissionUUID, - [paramKeys.assessmentType]: assessmentType, }), ).then(response => camelizeKeys(response.data)); @@ -150,7 +160,8 @@ export default StrictDict({ fetchSubmissionStatus, lockSubmission, updateGrade, - getFeedbackList, + getFeedbackFromList, + getFeedbackToList, unlockSubmission, batchUnlockSubmissions, }); diff --git a/src/data/services/lms/constants.js b/src/data/services/lms/constants.js index 3ce1541e..b3b92237 100644 --- a/src/data/services/lms/constants.js +++ b/src/data/services/lms/constants.js @@ -33,7 +33,6 @@ export const fileUploadResponseOptions = StrictDict({ export const paramKeys = StrictDict({ oraLocation: 'oraLocation', submissionUUID: 'submissionUUID', - assessmentType: 'assessmentType', }); export const oraTypes = StrictDict({ diff --git a/src/data/services/lms/urls.js b/src/data/services/lms/urls.js index 4d208455..b25e0e20 100644 --- a/src/data/services/lms/urls.js +++ b/src/data/services/lms/urls.js @@ -13,7 +13,8 @@ const fetchSubmissionStatusUrl = () => `${baseEsgUrl()}submission/status`; const fetchSubmissionLockUrl = () => `${baseEsgUrl()}submission/lock`; const batchUnlockSubmissionsUrl = () => `${baseEsgUrl()}submission/batch/unlock`; const updateSubmissionGradeUrl = () => `${baseEsgUrl()}submission/grade`; -const getFeedbackSubmissionsUrl = () => `${baseEsgUrl()}assessments/feedback`; +const assessmentsFeedbackFromUrl = () => `${baseEsgUrl()}assessments/feedback/from/`; +const assessmentFeedbackToUrl = () => `${baseEsgUrl()}assessments/feedback/to/`; const course = (courseId) => `${baseUrl()}/courses/${courseId}`; @@ -35,5 +36,6 @@ export default StrictDict({ course, openResponse, ora, - getFeedbackSubmissionsUrl, + assessmentsFeedbackFromUrl, + assessmentFeedbackToUrl, }); From 8b2dbd5ef064f28ba8d4daf64ddaeae3b497a388 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Wed, 28 Feb 2024 09:54:09 -0500 Subject: [PATCH 18/24] chore: update dependencies and snapshots --- package-lock.json | 203 +++++++++++++++++- .../SubmissionsTable.test.jsx.snap | 52 ++++- 2 files changed, 245 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8125a1a1..1c0d8488 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,9 +18,10 @@ "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.2.0", - "@openedx/paragon": "21.11.3", + "@openedx/paragon": "^22.0.0", "@redux-beacon/segment": "^1.1.0", "@reduxjs/toolkit": "^1.6.1", + "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.0.0", "@zip.js/zip.js": "^2.4.6", "axios": "^0.28.0", @@ -62,7 +63,7 @@ "@edx/react-unit-test-utils": "2.0.0", "@edx/reactifex": "^2.1.1", "@openedx/frontend-build": "13.0.28", - "@testing-library/jest-dom": "^6.0.0", + "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "12.1.5", "axios-mock-adapter": "^1.20.0", "fetch-mock": "^9.11.0", @@ -2260,6 +2261,99 @@ "node": ">=6" } }, + "node_modules/@edx/frontend-component-footer/node_modules/@openedx/paragon": { + "version": "21.13.1", + "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-21.13.1.tgz", + "integrity": "sha512-sLL+Z3ZWIRM6x+OrKZV0S7/SQpEcSeRcDm7E3FzhsnAWudsJCTELvSW+84uy/8dwV7mJhttsBPqQEtNafbCyYA==", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.1.1", + "@fortawesome/react-fontawesome": "^0.1.18", + "@popperjs/core": "^2.11.4", + "bootstrap": "^4.6.2", + "chalk": "^4.1.2", + "child_process": "^1.0.2", + "classnames": "^2.3.1", + "email-prop-type": "^3.0.0", + "file-selector": "^0.6.0", + "font-awesome": "^4.7.0", + "glob": "^8.0.3", + "inquirer": "^8.2.5", + "lodash.uniqby": "^4.7.0", + "mailto-link": "^2.0.0", + "prop-types": "^15.8.1", + "react-bootstrap": "^1.6.5", + "react-colorful": "^5.6.1", + "react-dropzone": "^14.2.1", + "react-focus-on": "^3.5.4", + "react-imask": "^7.1.3", + "react-loading-skeleton": "^3.1.0", + "react-popper": "^2.2.5", + "react-proptype-conditional-require": "^1.0.4", + "react-responsive": "^8.2.0", + "react-table": "^7.7.0", + "react-transition-group": "^4.4.2", + "tabbable": "^5.3.3", + "uncontrollable": "^7.2.1", + "uuid": "^9.0.0" + }, + "bin": { + "paragon": "bin/paragon-scripts.js" + }, + "peerDependencies": { + "react": "^16.8.6 || ^17.0.0", + "react-dom": "^16.8.6 || ^17.0.0", + "react-intl": "^5.25.1 || ^6.4.0" + } + }, + "node_modules/@edx/frontend-component-footer/node_modules/@openedx/paragon/node_modules/@fortawesome/react-fontawesome": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz", + "integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.x" + } + }, + "node_modules/@edx/frontend-component-footer/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@edx/frontend-component-footer/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@edx/frontend-component-footer/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@edx/frontend-component-header": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-5.0.2.tgz", @@ -2328,6 +2422,99 @@ "node": ">=6" } }, + "node_modules/@edx/frontend-component-header/node_modules/@openedx/paragon": { + "version": "21.13.1", + "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-21.13.1.tgz", + "integrity": "sha512-sLL+Z3ZWIRM6x+OrKZV0S7/SQpEcSeRcDm7E3FzhsnAWudsJCTELvSW+84uy/8dwV7mJhttsBPqQEtNafbCyYA==", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.1.1", + "@fortawesome/react-fontawesome": "^0.1.18", + "@popperjs/core": "^2.11.4", + "bootstrap": "^4.6.2", + "chalk": "^4.1.2", + "child_process": "^1.0.2", + "classnames": "^2.3.1", + "email-prop-type": "^3.0.0", + "file-selector": "^0.6.0", + "font-awesome": "^4.7.0", + "glob": "^8.0.3", + "inquirer": "^8.2.5", + "lodash.uniqby": "^4.7.0", + "mailto-link": "^2.0.0", + "prop-types": "^15.8.1", + "react-bootstrap": "^1.6.5", + "react-colorful": "^5.6.1", + "react-dropzone": "^14.2.1", + "react-focus-on": "^3.5.4", + "react-imask": "^7.1.3", + "react-loading-skeleton": "^3.1.0", + "react-popper": "^2.2.5", + "react-proptype-conditional-require": "^1.0.4", + "react-responsive": "^8.2.0", + "react-table": "^7.7.0", + "react-transition-group": "^4.4.2", + "tabbable": "^5.3.3", + "uncontrollable": "^7.2.1", + "uuid": "^9.0.0" + }, + "bin": { + "paragon": "bin/paragon-scripts.js" + }, + "peerDependencies": { + "react": "^16.8.6 || ^17.0.0", + "react-dom": "^16.8.6 || ^17.0.0", + "react-intl": "^5.25.1 || ^6.4.0" + } + }, + "node_modules/@edx/frontend-component-header/node_modules/@openedx/paragon/node_modules/@fortawesome/react-fontawesome": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz", + "integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.x" + } + }, + "node_modules/@edx/frontend-component-header/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@edx/frontend-component-header/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@edx/frontend-component-header/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@edx/frontend-platform": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-7.1.0.tgz", @@ -5314,9 +5501,9 @@ } }, "node_modules/@openedx/paragon": { - "version": "21.11.3", - "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-21.11.3.tgz", - "integrity": "sha512-czdb493pDs76+riJTPTr7NmHvaTLuFKGjoNNJP80jiTqDdmqKys03ZbpsEdh6Dk/l8y44uyS8FaOSLglZR4h6Q==", + "version": "22.1.1", + "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-22.1.1.tgz", + "integrity": "sha512-XPRuV9zn7BeCIYfU5kE2XZ4YevjA0wfS/fuydB8Ta/aNY1dw9fQ7CjHOIfkZqDic4Jygusj/uhE/1WYJD8kvyw==", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/react-fontawesome": "^0.1.18", @@ -6174,7 +6361,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", - "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", "react-error-boundary": "^3.1.0" @@ -22642,7 +22828,6 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", - "dev": true, "dependencies": { "@babel/runtime": "^7.12.5" }, @@ -23078,7 +23263,7 @@ "version": "16.15.0", "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "dev": true, + "devOptional": true, "dependencies": { "object-assign": "^4.1.1", "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" @@ -23133,7 +23318,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.2.tgz", "integrity": "sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==", - "dev": true, + "devOptional": true, "dependencies": { "object-assign": "^4.1.1", "react-is": "^17.0.2", diff --git a/src/containers/ListView/__snapshots__/SubmissionsTable.test.jsx.snap b/src/containers/ListView/__snapshots__/SubmissionsTable.test.jsx.snap index b2a379f0..ead112ef 100644 --- a/src/containers/ListView/__snapshots__/SubmissionsTable.test.jsx.snap +++ b/src/containers/ListView/__snapshots__/SubmissionsTable.test.jsx.snap @@ -8,6 +8,15 @@ exports[`SubmissionsTable component component render tests snapshots snapshot: h > Date: Wed, 28 Feb 2024 10:20:01 -0500 Subject: [PATCH 19/24] test: replace edx/react-unit-test-utils for enzyme --- .../ReviewProblemStepsContent.test.jsx | 2 +- .../ReviewProblemStepsContent.test.jsx.snap | 256 +++++++++++++++--- .../CloseReviewConfirmModal.test.jsx | 2 +- .../ReviewProblemStepActions/index.test.jsx | 2 +- .../components/ErrorMessage/index.test.jsx | 2 +- .../OverrideGradeConfirmModal.test.jsx | 2 +- .../components/ResponseItem/index.test.jsx | 2 +- .../components/ResponsesList/index.test.jsx | 2 +- .../__snapshots__/index.test.jsx.snap | 133 ++++++++- .../StartGradingButton/index.test.jsx | 2 +- .../StopGradingConfirmModal.test.jsx | 2 +- .../OverrideGradeConfirmModal.test.jsx.snap | 152 +++++++++-- .../StopGradingConfirmModal.test.jsx.snap | 231 ++++++++++++++-- .../ReviewProblemStepsContent/index.test.jsx | 2 +- .../CloseReviewConfirmModal.test.jsx.snap | 152 +++++++++-- .../ReviewProblemStepsModal/index.test.jsx | 2 +- 16 files changed, 820 insertions(+), 126 deletions(-) diff --git a/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.test.jsx b/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.test.jsx index c7310f62..80ffe41a 100644 --- a/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.test.jsx +++ b/src/containers/ReviewProblemStepsModal/ReviewProblemStepsContent.test.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow } from '@edx/react-unit-test-utils'; import { selectors } from 'data/redux'; import { RequestKeys } from 'data/constants/requests'; diff --git a/src/containers/ReviewProblemStepsModal/__snapshots__/ReviewProblemStepsContent.test.jsx.snap b/src/containers/ReviewProblemStepsModal/__snapshots__/ReviewProblemStepsContent.test.jsx.snap index 4a57d2bf..7fe02589 100644 --- a/src/containers/ReviewProblemStepsModal/__snapshots__/ReviewProblemStepsContent.test.jsx.snap +++ b/src/containers/ReviewProblemStepsModal/__snapshots__/ReviewProblemStepsContent.test.jsx.snap @@ -1,56 +1,234 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ReviewContent component component render tests snapshot: failed, showRubric (errors only) 1`] = ` -
-
, + "_firstWorkInProgressHook": null, + "_forcedUpdate": false, + "_idCounter": 0, + "_instance": null, + "_isReRender": false, + "_newState": null, + "_numberOfReRenders": 0, + "_renderPhaseUpdates": null, + "_rendered":
+
+ +
+
, + "_rendering": false, + "_updater": Updater { + "_callbacks": Array [], + "_renderer": [Circular], + }, + "_workInProgressHook": null, + }, + "shallowWrapper":
- -
-
+
+ +
+
, +} `; exports[`ReviewContent component component render tests snapshot: hide rubric 1`] = ` -
-
, + "_firstWorkInProgressHook": null, + "_forcedUpdate": false, + "_idCounter": 0, + "_instance": null, + "_isReRender": false, + "_newState": null, + "_numberOfReRenders": 0, + "_renderPhaseUpdates": null, + "_rendered":
+
+ + + + + + + +
+
, + "_rendering": false, + "_updater": Updater { + "_callbacks": Array [], + "_renderer": [Circular], + }, + "_workInProgressHook": null, + }, + "shallowWrapper":
- - - + - - - - -
-
+ + + + + +
+
, +} `; exports[`ReviewContent component component render tests snapshot: show rubric 1`] = ` -
-
, + "_firstWorkInProgressHook": null, + "_forcedUpdate": false, + "_idCounter": 0, + "_instance": null, + "_isReRender": false, + "_newState": null, + "_numberOfReRenders": 0, + "_renderPhaseUpdates": null, + "_rendered":
+
+ + + + + + +
+
, + "_rendering": false, + "_updater": Updater { + "_callbacks": Array [], + "_renderer": [Circular], + }, + "_workInProgressHook": null, + }, + "shallowWrapper":
- - - + - - - -
-
+ + + + +
+
, +} `; diff --git a/src/containers/ReviewProblemStepsModal/components/CloseReviewConfirmModal.test.jsx b/src/containers/ReviewProblemStepsModal/components/CloseReviewConfirmModal.test.jsx index 85f89a3b..64884bbf 100644 --- a/src/containers/ReviewProblemStepsModal/components/CloseReviewConfirmModal.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/CloseReviewConfirmModal.test.jsx @@ -1,4 +1,4 @@ -import { shallow } from 'enzyme'; +import { shallow } from '@edx/react-unit-test-utils'; import { formatMessage } from 'testUtils'; import { CloseReviewConfirmModal } from './CloseReviewConfirmModal'; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx index dd9bcdf2..a7e98785 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepActions/index.test.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow } from '@edx/react-unit-test-utils'; import { formatMessage } from 'testUtils'; import { ReviewProblemStepActions } from '.'; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx index 12201c61..3ebf0a0b 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ErrorMessage/index.test.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow } from '@edx/react-unit-test-utils'; import { Alert } from '@edx/paragon'; import { Info } from '@edx/paragon/icons'; import ErrorMessage from '.'; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/OverrideGradeConfirmModal.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/OverrideGradeConfirmModal.test.jsx index 26d6378a..f8b64827 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/OverrideGradeConfirmModal.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/OverrideGradeConfirmModal.test.jsx @@ -1,4 +1,4 @@ -import { shallow } from 'enzyme'; +import { shallow } from '@edx/react-unit-test-utils'; import { formatMessage } from 'testUtils'; import { OverrideGradeConfirmModal } from './OverrideGradeConfirmModal'; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.test.jsx index 9a3264b8..d76d4b04 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/components/ResponseItem/index.test.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { shallow, mount } from 'enzyme'; +import { shallow, mount } from '@edx/react-unit-test-utils'; import ResponseItem from '.'; describe('ResponseItem component', () => { diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx index 6735b0b1..cbc6da6a 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/ResponsesList/index.test.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow } from '@edx/react-unit-test-utils'; import { formatMessage } from 'testUtils'; import messages from './messages'; import { ResponsesList } from '.'; diff --git a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/__snapshots__/index.test.jsx.snap b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/__snapshots__/index.test.jsx.snap index 6e87c818..d82a1c10 100644 --- a/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/__snapshots__/index.test.jsx.snap +++ b/src/containers/ReviewProblemStepsModal/components/ReviewProblemStepsContent/components/StartGradingButton/__snapshots__/index.test.jsx.snap @@ -1,18 +1,125 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StartGradingButton component component snapshots hide: renders empty component if hook.hide is true 1`] = `""`; +exports[`StartGradingButton component component snapshots hide: renders empty component if hook.hide is true 1`] = ` +ReactShallowRenderer { + "shallowRenderer": ReactShallowRenderer { + "_context": Object {}, + "_didScheduleRenderPhaseUpdate": false, + "_dispatcher": Object { + "readContext": [Function], + "useCallback": [Function], + "useContext": [Function], + "useDebugValue": [Function], + "useDeferredValue": [Function], + "useEffect": [Function], + "useId": [Function], + "useImperativeHandle": [Function], + "useInsertionEffect": [Function], + "useLayoutEffect": [Function], + "useMemo": [Function], + "useReducer": [Function], + "useRef": [Function], + "useResponder": [Function], + "useState": [Function], + "useSyncExternalStore": [Function], + "useTransition": [Function], + }, + "_element": , + "_firstWorkInProgressHook": null, + "_forcedUpdate": false, + "_idCounter": 0, + "_instance": null, + "_isReRender": false, + "_newState": null, + "_numberOfReRenders": 0, + "_renderPhaseUpdates": null, + "_rendered": null, + "_rendering": false, + "_updater": Updater { + "_callbacks": Array [], + "_renderer": [Circular], + }, + "_workInProgressHook": null, + }, + "shallowWrapper": null, +} +`; exports[`StartGradingButton component component snapshots smoke test: forwards props to components from hooks 1`] = ` - -