From f76f3d64c97dc0a24ba133e2114ff3eb502e1a2e Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Fri, 31 Mar 2023 15:02:42 -0400 Subject: [PATCH] refactor: bulk management controls component --- .../GradesView/BulkManagementControls.jsx | 71 ---------- .../BulkManagementControls.test.jsx | 124 ------------------ .../__snapshots__/index.test.jsx.snap | 19 +++ .../BulkManagementControls/hooks.js | 18 +++ .../BulkManagementControls/hooks.test.js | 72 ++++++++++ .../BulkManagementControls/index.jsx | 32 +++++ .../BulkManagementControls/index.test.jsx | 32 +++++ .../messages.js} | 0 src/data/redux/hooks/actions.js | 7 + src/data/redux/hooks/actions.test.js | 12 ++ 10 files changed, 192 insertions(+), 195 deletions(-) delete mode 100644 src/components/GradesView/BulkManagementControls.jsx delete mode 100644 src/components/GradesView/BulkManagementControls.test.jsx create mode 100644 src/components/GradesView/BulkManagementControls/__snapshots__/index.test.jsx.snap create mode 100644 src/components/GradesView/BulkManagementControls/hooks.js create mode 100644 src/components/GradesView/BulkManagementControls/hooks.test.js create mode 100644 src/components/GradesView/BulkManagementControls/index.jsx create mode 100644 src/components/GradesView/BulkManagementControls/index.test.jsx rename src/components/GradesView/{BulkManagementControls.messages.js => BulkManagementControls/messages.js} (100%) diff --git a/src/components/GradesView/BulkManagementControls.jsx b/src/components/GradesView/BulkManagementControls.jsx deleted file mode 100644 index d7fcbc9..0000000 --- a/src/components/GradesView/BulkManagementControls.jsx +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint-disable react/sort-comp, react/button-has-type */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; - -import { views } from 'data/constants/app'; -import actions from 'data/actions'; -import selectors from 'data/selectors'; - -import NetworkButton from 'components/NetworkButton'; -import ImportGradesButton from './ImportGradesButton'; - -import messages from './BulkManagementControls.messages'; - -/** - * - * Provides download buttons for Bulk Management and Intervention reports, only if - * showBulkManagement is set in redus. - */ -export class BulkManagementControls extends React.Component { - constructor(props) { - super(props); - this.handleClickExportGrades = this.handleClickExportGrades.bind(this); - this.handleViewActivityLog = this.handleViewActivityLog.bind(this); - } - - handleClickExportGrades() { - this.props.downloadBulkGradesReport(); - window.location.assign(this.props.gradeExportUrl); - } - - handleViewActivityLog() { - this.props.setView(views.bulkManagementHistory); - } - - render() { - return this.props.showBulkManagement && ( -
- - -
- ); - } -} - -BulkManagementControls.defaultProps = { - showBulkManagement: false, -}; - -BulkManagementControls.propTypes = { - // redux - downloadBulkGradesReport: PropTypes.func.isRequired, - gradeExportUrl: PropTypes.string.isRequired, - showBulkManagement: PropTypes.bool, - setView: PropTypes.func.isRequired, -}; - -export const mapStateToProps = (state) => ({ - gradeExportUrl: selectors.root.gradeExportUrl(state), - showBulkManagement: selectors.root.showBulkManagement(state), -}); - -export const mapDispatchToProps = { - downloadBulkGradesReport: actions.grades.downloadReport.bulkGrades, - setView: actions.app.setView, -}; - -export default connect(mapStateToProps, mapDispatchToProps)(BulkManagementControls); diff --git a/src/components/GradesView/BulkManagementControls.test.jsx b/src/components/GradesView/BulkManagementControls.test.jsx deleted file mode 100644 index f9fa5e2..0000000 --- a/src/components/GradesView/BulkManagementControls.test.jsx +++ /dev/null @@ -1,124 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; - -import actions from 'data/actions'; -import selectors from 'data/selectors'; -import { views } from 'data/constants/app'; - -import { - BulkManagementControls, - mapStateToProps, - mapDispatchToProps, -} from './BulkManagementControls'; - -jest.mock('./ImportGradesButton', () => 'ImportGradesButton'); -jest.mock('components/NetworkButton', () => 'NetworkButton'); -jest.mock('data/selectors', () => ({ - __esModule: true, - default: { - root: { - gradeExportUrl: (state) => ({ gradeExportUrl: state }), - interventionExportUrl: (state) => ({ interventionExportUrl: state }), - showBulkManagement: (state) => ({ showBulkManagement: state }), - }, - }, -})); -jest.mock('data/actions', () => ({ - __esModule: true, - default: { - app: { setView: jest.fn() }, - grades: { - downloadReport: { - bulkGrades: jest.fn(), - intervention: jest.fn(), - }, - }, - }, -})); - -describe('BulkManagementControls', () => { - describe('component', () => { - let el; - let props = { - gradeExportUrl: 'gradesGoHere', - interventionExportUrl: 'interventionsGoHere', - }; - beforeEach(() => { - props = { - ...props, - downloadBulkGradesReport: jest.fn(), - downloadInterventionReport: jest.fn(), - setView: jest.fn(), - }; - }); - test('snapshot - empty if showBulkManagement is not truthy', () => { - expect(shallow()).toEqual({}); - }); - describe('behavior', () => { - const oldWindowLocation = window.location; - - beforeAll(() => { - delete window.location; - window.location = Object.defineProperties( - {}, - { - ...Object.getOwnPropertyDescriptors(oldWindowLocation), - assign: { - configurable: true, - value: jest.fn(), - }, - }, - ); - }); - beforeEach(() => { - window.location.assign.mockReset(); - el = shallow(); - }); - afterAll(() => { - // restore `window.location` to the `jsdom` `Location` object - window.location = oldWindowLocation; - }); - describe('handleViewActivityLog', () => { - it('calls props.setView(views.bulkManagementHistory)', () => { - el.instance().handleViewActivityLog(); - expect(props.setView).toHaveBeenCalledWith(views.bulkManagementHistory); - }); - }); - describe('handleClickExportGrades', () => { - const assertions = [ - 'calls props.downloadBulkGradesReport', - 'sets location to props.gradeExportUrl', - ]; - it(assertions.join(' and '), () => { - el.instance().handleClickExportGrades(); - expect(props.downloadBulkGradesReport).toHaveBeenCalled(); - expect(window.location.assign).toHaveBeenCalledWith(props.gradeExportUrl); - }); - }); - }); - }); - - describe('mapStateToProps', () => { - let mapped; - const testState = { do: 'not', test: 'me' }; - beforeEach(() => { - mapped = mapStateToProps(testState); - }); - test('gradeExportUrl from root.gradeExportUrl', () => { - expect(mapped.gradeExportUrl).toEqual(selectors.root.gradeExportUrl(testState)); - }); - test('showBulkManagement from root.showBulkManagement', () => { - expect(mapped.showBulkManagement).toEqual(selectors.root.showBulkManagement(testState)); - }); - }); - describe('mapDispatchToProps', () => { - test('downloadBulkGradesReport from actions.grades.downloadReport.bulkGrades', () => { - expect( - mapDispatchToProps.downloadBulkGradesReport, - ).toEqual(actions.grades.downloadReport.bulkGrades); - }); - test('setView from actions.app.setView', () => { - expect(mapDispatchToProps.setView).toEqual(actions.app.setView); - }); - }); -}); diff --git a/src/components/GradesView/BulkManagementControls/__snapshots__/index.test.jsx.snap b/src/components/GradesView/BulkManagementControls/__snapshots__/index.test.jsx.snap new file mode 100644 index 0000000..a97d82f --- /dev/null +++ b/src/components/GradesView/BulkManagementControls/__snapshots__/index.test.jsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BulkManagementControls render snapshot - show - network and import buttons 1`] = ` +
+ + +
+`; diff --git a/src/components/GradesView/BulkManagementControls/hooks.js b/src/components/GradesView/BulkManagementControls/hooks.js new file mode 100644 index 0000000..21786f8 --- /dev/null +++ b/src/components/GradesView/BulkManagementControls/hooks.js @@ -0,0 +1,18 @@ +import { actions, selectors } from 'data/redux/hooks'; + +export const useBulkManagementControlsData = () => { + const gradeExportUrl = selectors.root.useGradeExportUrl(); + const showBulkManagement = selectors.root.useShowBulkManagement(); + const downloadBulkGradesReport = actions.grades.downloadReport.useBulkGrades(); + + const handleClickExportGrades = () => { + downloadBulkGradesReport(); + window.location.assign(gradeExportUrl); + }; + + return { + show: showBulkManagement, + handleClickExportGrades, + }; +}; +export default useBulkManagementControlsData; diff --git a/src/components/GradesView/BulkManagementControls/hooks.test.js b/src/components/GradesView/BulkManagementControls/hooks.test.js new file mode 100644 index 0000000..59fb6fd --- /dev/null +++ b/src/components/GradesView/BulkManagementControls/hooks.test.js @@ -0,0 +1,72 @@ +import { actions, selectors } from 'data/redux/hooks'; + +import useBulkManagementControlsData from './hooks'; + +jest.mock('data/redux/hooks', () => ({ + actions: { + grades: { + downloadReport: { useBulkGrades: jest.fn() }, + }, + }, + selectors: { + root: { + useGradeExportUrl: jest.fn(), + useShowBulkManagement: jest.fn(), + }, + }, +})); + +const downloadBulkGrades = jest.fn(); +actions.grades.downloadReport.useBulkGrades.mockReturnValue(downloadBulkGrades); +const gradeExportUrl = 'test-grade-export-url'; +selectors.root.useGradeExportUrl.mockReturnValue(gradeExportUrl); +selectors.root.useShowBulkManagement.mockReturnValue(true); + +let hook; +describe('useBulkManagementControlsData', () => { + const oldWindowLocation = window.location; + beforeAll(() => { + delete window.location; + window.location = Object.defineProperties( + {}, + { + ...Object.getOwnPropertyDescriptors(oldWindowLocation), + assign: { configurable: true, value: jest.fn() }, + }, + ); + }); + beforeEach(() => { + window.location.assign.mockReset(); + hook = useBulkManagementControlsData(); + }); + afterAll(() => { + // restore `window.location` to the `jsdom` `Location` object + window.location = oldWindowLocation; + }); + describe('initialization', () => { + it('initializes redux hooks', () => { + expect(selectors.root.useGradeExportUrl).toHaveBeenCalledWith(); + expect(selectors.root.useShowBulkManagement).toHaveBeenCalledWith(); + expect(actions.grades.downloadReport.useBulkGrades).toHaveBeenCalledWith(); + }); + }); + describe('output', () => { + it('forwards show from showBulkManagement', () => { + expect(hook.show).toEqual(true); + selectors.root.useShowBulkManagement.mockReturnValue(false); + hook = useBulkManagementControlsData(); + expect(hook.show).toEqual(false); + }); + describe('handleClickExportGrades', () => { + beforeEach(() => { + hook.handleClickExportGrades(); + }); + it('downloads bulk grades report', () => { + expect(downloadBulkGrades).toHaveBeenCalledWith(); + }); + it('sets window location to grade export url', () => { + expect(window.location.assign).toHaveBeenCalledWith(gradeExportUrl); + }); + }); + }); +}); diff --git a/src/components/GradesView/BulkManagementControls/index.jsx b/src/components/GradesView/BulkManagementControls/index.jsx new file mode 100644 index 0000000..911c33a --- /dev/null +++ b/src/components/GradesView/BulkManagementControls/index.jsx @@ -0,0 +1,32 @@ +/* eslint-disable react/sort-comp, react/button-has-type */ +import React from 'react'; + +import NetworkButton from 'components/NetworkButton'; +import ImportGradesButton from '../ImportGradesButton'; + +import useBulkManagementControlsData from './hooks'; +import messages from './messages'; + +/** + * + * Provides download buttons for Bulk Management and Intervention reports, only if + * showBulkManagement is set in redus. + */ +export const BulkManagementControls = () => { + const { + show, + handleClickExportGrades, + } = useBulkManagementControlsData(); + + return show && ( +
+ + +
+ ); +}; + +export default BulkManagementControls; diff --git a/src/components/GradesView/BulkManagementControls/index.test.jsx b/src/components/GradesView/BulkManagementControls/index.test.jsx new file mode 100644 index 0000000..e5e3ca6 --- /dev/null +++ b/src/components/GradesView/BulkManagementControls/index.test.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import useBulkManagementControlsData from './hooks'; +import BulkManagementControls from '.'; + +jest.mock('../ImportGradesButton', () => 'ImportGradesButton'); +jest.mock('components/NetworkButton', () => 'NetworkButton'); + +jest.mock('./hooks', () => jest.fn()); + +const hookProps = { + show: true, + handleClickExportGrades: jest.fn(), +}; +useBulkManagementControlsData.mockReturnValue(hookProps); + +describe('BulkManagementControls', () => { + describe('behavior', () => { + shallow(); + expect(useBulkManagementControlsData).toHaveBeenCalledWith(); + }); + describe('render', () => { + test('snapshot - show - network and import buttons', () => { + expect(shallow()).toMatchSnapshot(); + }); + test('snapshot - empty if show is not truthy', () => { + useBulkManagementControlsData.mockReturnValueOnce({ ...hookProps, show: false }); + expect(shallow().isEmptyRender()).toEqual(true); + }); + }); +}); diff --git a/src/components/GradesView/BulkManagementControls.messages.js b/src/components/GradesView/BulkManagementControls/messages.js similarity index 100% rename from src/components/GradesView/BulkManagementControls.messages.js rename to src/components/GradesView/BulkManagementControls/messages.js diff --git a/src/data/redux/hooks/actions.js b/src/data/redux/hooks/actions.js index 09c01c0..b1b5b4e 100644 --- a/src/data/redux/hooks/actions.js +++ b/src/data/redux/hooks/actions.js @@ -17,7 +17,14 @@ const filters = StrictDict({ useUpdateTrack: actionHook(actions.filters.update.track), }); +const grades = StrictDict({ + downloadReport: { + useBulkGrades: actionHook(actions.grades.downloadReport.bulkGrades), + }, +}); + export default StrictDict({ app, filters, + grades, }); diff --git a/src/data/redux/hooks/actions.test.js b/src/data/redux/hooks/actions.test.js index cbf0384..5e66d03 100644 --- a/src/data/redux/hooks/actions.test.js +++ b/src/data/redux/hooks/actions.test.js @@ -15,6 +15,11 @@ jest.mock('data/actions', () => ({ assignmentLimits: jest.fn(), }, }, + grades: { + downloadReport: { + bulkGrades: jest.fn(), + }, + }, })); jest.mock('./utils', () => ({ actionHook: (action) => ({ actionHook: action }), @@ -49,4 +54,11 @@ describe('action hooks', () => { ); testActionHook(hookKeys.useUpdateTrack, actionGroup.updateTrack); }); + describe('grades', () => { + beforeEach(() => { hooks = actionHooks.grades; }); + test('downloadReport.useBulkGrades', () => { + expect(hooks.downloadReport.useBulkGrades) + .toEqual(actionHook(actions.grades.downloadReport.bulkGrades)); + }); + }); });