From 2b90f9fe98035210d07037dfeddacac05429e27b Mon Sep 17 00:00:00 2001 From: Alie Langston Date: Tue, 16 Jul 2024 14:27:04 -0400 Subject: [PATCH] feat: add allowances tab --- src/index.scss | 8 +- .../__snapshots__/index.test.jsx.snap | 18 ++ .../ExamsPage/components/AllowanceList.jsx | 44 ++++ .../ExamsPage/components/AllowanceList.scss | 18 ++ .../components/AllowanceList.test.jsx | 12 + .../ExamsPage/components/ExamSelection.jsx | 8 +- .../components/ExamSelection.test.jsx | 4 + .../__snapshots__/AllowanceList.test.jsx.snap | 214 ++++++++++++++++++ src/pages/ExamsPage/data/reducer.js | 1 + src/pages/ExamsPage/data/reducer.test.js | 3 + src/pages/ExamsPage/data/selectors.js | 2 + src/pages/ExamsPage/hooks.js | 5 + src/pages/ExamsPage/index.jsx | 25 +- src/pages/ExamsPage/index.test.jsx | 6 + src/pages/ExamsPage/messages.js | 20 ++ 15 files changed, 379 insertions(+), 9 deletions(-) create mode 100644 src/pages/ExamsPage/components/AllowanceList.jsx create mode 100644 src/pages/ExamsPage/components/AllowanceList.scss create mode 100644 src/pages/ExamsPage/components/AllowanceList.test.jsx create mode 100644 src/pages/ExamsPage/components/__snapshots__/AllowanceList.test.jsx.snap diff --git a/src/index.scss b/src/index.scss index 04fbe8e..5211a7d 100644 --- a/src/index.scss +++ b/src/index.scss @@ -1,4 +1,4 @@ -@import "@edx/brand/paragon/fonts.scss"; -@import "@edx/brand/paragon/variables.scss"; -@import "@openedx/paragon/scss/core/core.scss"; -@import "@edx/brand/paragon/overrides.scss"; +@import "~@edx/brand/paragon/fonts"; +@import "~@edx/brand/paragon/variables"; +@import "~@openedx/paragon/scss/core/core"; +@import "~@edx/brand/paragon/overrides"; diff --git a/src/pages/ExamsPage/__snapshots__/index.test.jsx.snap b/src/pages/ExamsPage/__snapshots__/index.test.jsx.snap index 9b4cf6a..1b299c9 100644 --- a/src/pages/ExamsPage/__snapshots__/index.test.jsx.snap +++ b/src/pages/ExamsPage/__snapshots__/index.test.jsx.snap @@ -92,6 +92,15 @@ exports[`ExamsPage snapshots exams and attempts loaded 1`] = ` > Review Dashboard + + Allowances + Review Dashboard + + Allowances + { + const { formatMessage } = useIntl(); + + return ( + <> +
+

{formatMessage(messages.allowanceDashboardTabTitle)}

+ +
+
+ { allowances.length === 0 + ? ( + <> +

{formatMessage(messages.noAllowancesHeader)}

+

{formatMessage(messages.noAllowancesBody)}

+ + + ) + : null } +
+ + ); +}; + +AllowanceList.propTypes = { + allowances: PropTypes.arrayOf(PropTypes.object).isRequired, // eslint-disable-line react/forbid-prop-types +}; + +export default AllowanceList; diff --git a/src/pages/ExamsPage/components/AllowanceList.scss b/src/pages/ExamsPage/components/AllowanceList.scss new file mode 100644 index 0000000..8fd55d3 --- /dev/null +++ b/src/pages/ExamsPage/components/AllowanceList.scss @@ -0,0 +1,18 @@ +.allowances-body { + display: inline-block; + text-align: center; + width: 100%; + padding-bottom: 50px; +} + +.allowances-header { + width: 100%; + padding: 10px; + position: relative; + + .header-allowance-button { + position: absolute; + top: 10px; + right: 10px; + } +} diff --git a/src/pages/ExamsPage/components/AllowanceList.test.jsx b/src/pages/ExamsPage/components/AllowanceList.test.jsx new file mode 100644 index 0000000..4e77d19 --- /dev/null +++ b/src/pages/ExamsPage/components/AllowanceList.test.jsx @@ -0,0 +1,12 @@ +import { render } from '@testing-library/react'; + +import AllowanceList from './AllowanceList'; + +// normally mocked for unit tests but required for rendering/snapshots +// jest.unmock('react'); + +describe('AllowanceList', () => { + it('Test that the AllowanceList matches snapshot', () => { + expect(render()).toMatchSnapshot(); + }); +}); diff --git a/src/pages/ExamsPage/components/ExamSelection.jsx b/src/pages/ExamsPage/components/ExamSelection.jsx index eddf15f..52c2e7f 100644 --- a/src/pages/ExamsPage/components/ExamSelection.jsx +++ b/src/pages/ExamsPage/components/ExamSelection.jsx @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; import messages from '../messages'; -const ExamSelection = ({ exams, onSelect }) => { +const ExamSelection = ({ exams, onSelect, isDisabled }) => { const { formatMessage } = useIntl(); const [searchText, setSearchText] = useState(''); const getMenuItems = () => { @@ -38,6 +38,7 @@ const ExamSelection = ({ exams, onSelect }) => {
{ getMenuItems() } @@ -51,6 +52,11 @@ ExamSelection.propTypes = { name: PropTypes.string, })).isRequired, onSelect: PropTypes.func.isRequired, + isDisabled: PropTypes.bool, +}; + +ExamSelection.defaultProps = { + isDisabled: false, }; export default ExamSelection; diff --git a/src/pages/ExamsPage/components/ExamSelection.test.jsx b/src/pages/ExamsPage/components/ExamSelection.test.jsx index 9d35e7b..69a225d 100644 --- a/src/pages/ExamsPage/components/ExamSelection.test.jsx +++ b/src/pages/ExamsPage/components/ExamSelection.test.jsx @@ -57,4 +57,8 @@ describe('ExamSelection', () => { expect(mockHandleSelectExam).toHaveBeenCalledTimes(1); expect(mockHandleSelectExam).toHaveBeenCalledWith(27); }); + it('button disabled when isDisabled is true', () => { + renderWithoutError(); + expect(screen.getByText('Select an exam')).toBeDisabled(); + }); }); diff --git a/src/pages/ExamsPage/components/__snapshots__/AllowanceList.test.jsx.snap b/src/pages/ExamsPage/components/__snapshots__/AllowanceList.test.jsx.snap new file mode 100644 index 0000000..5e868a1 --- /dev/null +++ b/src/pages/ExamsPage/components/__snapshots__/AllowanceList.test.jsx.snap @@ -0,0 +1,214 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AllowanceList Test that the AllowanceList matches snapshot 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+

+ + Allowances + +

+ +
+
+

+ + No Allowances + +

+

+ + Need to grant an allowance? Get started here. + +

+ +
+
+ , + "container":
+
+

+ + Allowances + +

+ +
+
+

+ + No Allowances + +

+

+ + Need to grant an allowance? Get started here. + +

+ +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/pages/ExamsPage/data/reducer.js b/src/pages/ExamsPage/data/reducer.js index b52f8be..d4611f7 100644 --- a/src/pages/ExamsPage/data/reducer.js +++ b/src/pages/ExamsPage/data/reducer.js @@ -6,6 +6,7 @@ export const initialState = { currentExamIndex: null, examsList: [], attemptsList: [], + allowancesList: [], }; const getStatusFromAction = (action, status) => { diff --git a/src/pages/ExamsPage/data/reducer.test.js b/src/pages/ExamsPage/data/reducer.test.js index 4990986..011faf1 100644 --- a/src/pages/ExamsPage/data/reducer.test.js +++ b/src/pages/ExamsPage/data/reducer.test.js @@ -40,6 +40,7 @@ describe('ExamsPage reducer', () => { }, ], attemptsList: [], + allowancesList: [], }); }); }); @@ -98,6 +99,7 @@ describe('ExamsPage reducer', () => { attempt_id: 1, }, ], + allowancesList: [], }); }); }); @@ -327,6 +329,7 @@ describe('ExamsPage reducer', () => { currentExamIndex: null, examsList: [], attemptsList: [], + allowancesList: [], courseId: 'course-v1:edX+Test+Test', }); }); diff --git a/src/pages/ExamsPage/data/selectors.js b/src/pages/ExamsPage/data/selectors.js index fc0117d..89cc5b5 100644 --- a/src/pages/ExamsPage/data/selectors.js +++ b/src/pages/ExamsPage/data/selectors.js @@ -6,3 +6,5 @@ export const currentExam = state => state.exams.examsList[state.exams.currentExa export const courseExamAttemptsList = state => state.exams.attemptsList; export const courseId = state => state.exams.courseId; + +export const courseAllowancesList = state => state.exams.allowancesList; diff --git a/src/pages/ExamsPage/hooks.js b/src/pages/ExamsPage/hooks.js index 72c8a8a..2e66af7 100644 --- a/src/pages/ExamsPage/hooks.js +++ b/src/pages/ExamsPage/hooks.js @@ -117,3 +117,8 @@ export const useButtonStateFromRequestStatus = (requestKey) => { return ''; }; }; + +export const useAllowancesData = () => { + const allowancesList = useSelector(selectors.courseAllowancesList); + return { allowancesList }; +}; diff --git a/src/pages/ExamsPage/index.jsx b/src/pages/ExamsPage/index.jsx index ed1f694..7b75c63 100644 --- a/src/pages/ExamsPage/index.jsx +++ b/src/pages/ExamsPage/index.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { Tabs, Tab, Container } from '@openedx/paragon'; @@ -6,16 +6,23 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import messages from './messages'; import { - useExamsData, useInitializeExamsPage, useExamAttemptsData, + useExamsData, + useInitializeExamsPage, + useExamAttemptsData, + useAllowancesData, } from './hooks'; import AttemptList from './components/AttemptList'; import ExternalReviewDashboard from './components/ExternalReviewDashboard'; import ExamSelection from './components/ExamSelection'; +import AllowanceList from './components/AllowanceList'; import './index.scss'; const ExamsPage = ({ courseId }) => { useInitializeExamsPage(courseId); + + const [key, setKey] = useState('attempts'); + const { formatMessage } = useIntl(); const { currentExam, @@ -25,19 +32,29 @@ const ExamsPage = ({ courseId }) => { const { attemptsList, } = useExamAttemptsData(); + const { + allowancesList, + } = useAllowancesData(); return ( - + - + setKey(k)} + > + + + ); diff --git a/src/pages/ExamsPage/index.test.jsx b/src/pages/ExamsPage/index.test.jsx index 4dddca0..f713c21 100644 --- a/src/pages/ExamsPage/index.test.jsx +++ b/src/pages/ExamsPage/index.test.jsx @@ -11,6 +11,7 @@ jest.mock('./hooks', () => ({ useInitializeExamsPage: jest.fn(), useExamAttemptsData: jest.fn(), useExamsData: jest.fn(), + useAllowancesData: jest.fn(), useFetchExamAttempts: jest.fn(), useDeleteExamAttempt: jest.fn(), useModifyExamAttempt: jest.fn(), @@ -20,6 +21,7 @@ jest.mock('./hooks', () => ({ describe('ExamsPage', () => { beforeAll(() => { hooks.useExamAttemptsData.mockReturnValue(testUtils.defaultAttemptsData); + hooks.useAllowancesData.mockReturnValue({ allowancesList: [] }); }); describe('snapshots', () => { test('exams and attempts loaded', () => { @@ -44,5 +46,9 @@ describe('ExamsPage', () => { screen.getByText('Review Dashboard').click(); expect(screen.getByTestId('review_dash')).toBeInTheDocument(); }); + test('switch tabs to allowances', () => { + screen.getByText('Allowances').click(); + expect(screen.getByTestId('allowances')).toBeInTheDocument(); + }); }); }); diff --git a/src/pages/ExamsPage/messages.js b/src/pages/ExamsPage/messages.js index 9e5a6b2..52ed29d 100644 --- a/src/pages/ExamsPage/messages.js +++ b/src/pages/ExamsPage/messages.js @@ -290,6 +290,26 @@ const messages = defineMessages({ defaultMessage: 'Please select an exam from the dropdown above.', description: 'A prompt to select an exam before being able to open the external review dashboard.', }, + allowanceDashboardTabTitle: { + id: 'ExamsPage.allowanceDashboardTabTitle', + defaultMessage: 'Allowances', + description: 'Title for the allowances tab', + }, + allowanceButton: { + id: 'ExamsPage.allowanceButton', + defaultMessage: 'Add allowance', + description: 'Label for for a button to add an allowance', + }, + noAllowancesHeader: { + id: 'ExamsPage.noAllowancesHeader', + defaultMessage: 'No Allowances', + description: 'Header shown when no allowances have been created', + }, + noAllowancesBody: { + id: 'ExamsPage.noAllowancesBody', + defaultMessage: 'Need to grant an allowance? Get started here.', + description: 'Text shown when no allowances have been created', + }, }); export default messages;