From 6b149e9ce0afbc2db40e5650712c3d460c4be25e Mon Sep 17 00:00:00 2001 From: Ben Warzeski Date: Thu, 11 May 2023 13:29:14 -0400 Subject: [PATCH] refactor: PageButtons component modernization --- .../PageButtons/PageButtons.test.jsx | 96 ------------- .../__snapshots__/PageButtons.test.jsx.snap | 133 ------------------ .../__snapshots__/index.test.jsx.snap | 37 +++++ .../GradesView/PageButtons/hooks.js | 34 +++++ .../GradesView/PageButtons/hooks.test.js | 77 ++++++++++ .../GradesView/PageButtons/index.jsx | 92 ++++-------- .../GradesView/PageButtons/index.test.jsx | 53 +++++++ 7 files changed, 228 insertions(+), 294 deletions(-) delete mode 100644 src/components/GradesView/PageButtons/PageButtons.test.jsx delete mode 100644 src/components/GradesView/PageButtons/__snapshots__/PageButtons.test.jsx.snap create mode 100644 src/components/GradesView/PageButtons/__snapshots__/index.test.jsx.snap create mode 100644 src/components/GradesView/PageButtons/hooks.js create mode 100644 src/components/GradesView/PageButtons/hooks.test.js create mode 100644 src/components/GradesView/PageButtons/index.test.jsx diff --git a/src/components/GradesView/PageButtons/PageButtons.test.jsx b/src/components/GradesView/PageButtons/PageButtons.test.jsx deleted file mode 100644 index 4168022..0000000 --- a/src/components/GradesView/PageButtons/PageButtons.test.jsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; - -import selectors from 'data/selectors'; -import thunkActions from 'data/thunkActions'; - -import { PageButtons, mapStateToProps, mapDispatchToProps } from '.'; - -jest.mock('@edx/paragon', () => ({ - Button: () => 'Button', -})); -jest.mock('data/selectors', () => ({ - __esModule: true, - default: { - grades: { - nextPage: jest.fn(state => ({ nextPage: state })), - prevPage: jest.fn(state => ({ prevPage: state })), - }, - }, -})); - -jest.mock('data/thunkActions', () => ({ - __esModule: true, - default: { - grades: { - fetchPrevNextGrades: jest.fn(), - }, - }, -})); - -let props; -let el; -describe('PageButtons component', () => { - beforeEach(() => { - props = { - getPrevNextGrades: jest.fn(), - nextPage: 'NEXT PAGE', - prevPage: 'prev PAGE', - }; - }); - describe('snapshots', () => { - beforeEach(() => { - el = shallow(); - el.instance.fetchNextGrades = jest.fn().mockName('fetchNextGrades'); - el.instance.fetchPrevGrades = jest.fn().mockName('fetchPrevGrades'); - }); - test('buttons enabled with both endpoints provided', () => { - expect(el.instance().render()).toMatchSnapshot(); - }); - test('nextPage disabled if not provided', () => { - el.setProps({ nextPage: undefined }); - expect(el.instance().render()).toMatchSnapshot(); - }); - test('prevPage disabled if not provided', () => { - el.setProps({ prevPage: undefined }); - expect(el.instance().render()).toMatchSnapshot(); - }); - }); - describe('behavior', () => { - beforeEach(() => { - el = shallow(); - }); - describe('getPrevGrades', () => { - it('calls props.getPrevNextGrades with props.prevPage', () => { - el.instance().getPrevGrades(); - expect(props.getPrevNextGrades).toHaveBeenCalledWith(props.prevPage); - }); - }); - describe('getNextGrades', () => { - it('calls props.getPrevNextGrades with props.nextPage', () => { - el.instance().getNextGrades(); - expect(props.getPrevNextGrades).toHaveBeenCalledWith(props.nextPage); - }); - }); - }); - describe('mapStateToProps', () => { - const testState = { l: 'eeeerroooooy', j: 'jjjjeeeeeeenkins' }; - let mapped; - beforeEach(() => { - mapped = mapStateToProps(testState); - }); - test('nextPage from grades.nextPage', () => { - expect(mapped.nextPage).toEqual(selectors.grades.nextPage(testState)); - }); - test('prevPage from grades.prevPage', () => { - expect(mapped.prevPage).toEqual(selectors.grades.prevPage(testState)); - }); - }); - describe('mapDispatchToProps', () => { - test('getPrevNextGrades from thunkActions.grades.fetchPrevNextGrades', () => { - expect( - mapDispatchToProps.getPrevNextGrades, - ).toEqual(thunkActions.grades.fetchPrevNextGrades); - }); - }); -}); diff --git a/src/components/GradesView/PageButtons/__snapshots__/PageButtons.test.jsx.snap b/src/components/GradesView/PageButtons/__snapshots__/PageButtons.test.jsx.snap deleted file mode 100644 index 144c7c0..0000000 --- a/src/components/GradesView/PageButtons/__snapshots__/PageButtons.test.jsx.snap +++ /dev/null @@ -1,133 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PageButtons component snapshots buttons enabled with both endpoints provided 1`] = ` -
- - -
-`; - -exports[`PageButtons component snapshots nextPage disabled if not provided 1`] = ` -
- - -
-`; - -exports[`PageButtons component snapshots prevPage disabled if not provided 1`] = ` -
- - -
-`; diff --git a/src/components/GradesView/PageButtons/__snapshots__/index.test.jsx.snap b/src/components/GradesView/PageButtons/__snapshots__/index.test.jsx.snap new file mode 100644 index 0000000..8fb8ffa --- /dev/null +++ b/src/components/GradesView/PageButtons/__snapshots__/index.test.jsx.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PageButtons component render snapshot 1`] = ` +
+ + +
+`; diff --git a/src/components/GradesView/PageButtons/hooks.js b/src/components/GradesView/PageButtons/hooks.js new file mode 100644 index 0000000..f102342 --- /dev/null +++ b/src/components/GradesView/PageButtons/hooks.js @@ -0,0 +1,34 @@ +import { useIntl } from '@edx/frontend-platform/i18n'; + +import { selectors, thunkActions } from 'data/redux/hooks'; +import messages from './messages'; + +export const usePageButtonsData = () => { + const { formatMessage } = useIntl(); + + const { nextPage, prevPage } = selectors.grades.useGradeData(); + const getPrevNextGrades = thunkActions.grades.useFetchPrevNextGrades(); + + const getPrevGrades = () => { + getPrevNextGrades(prevPage); + }; + + const getNextGrades = () => { + getPrevNextGrades(nextPage); + }; + + return { + prev: { + disabled: !prevPage, + onClick: getPrevGrades, + text: formatMessage(messages.prevPage), + }, + next: { + disabled: !nextPage, + onClick: getNextGrades, + text: formatMessage(messages.nextPage), + }, + }; +}; + +export default usePageButtonsData; diff --git a/src/components/GradesView/PageButtons/hooks.test.js b/src/components/GradesView/PageButtons/hooks.test.js new file mode 100644 index 0000000..8d96bec --- /dev/null +++ b/src/components/GradesView/PageButtons/hooks.test.js @@ -0,0 +1,77 @@ +import { useIntl } from '@edx/frontend-platform/i18n'; + +import { formatMessage } from 'testUtils'; +import { selectors, thunkActions } from 'data/redux/hooks'; + +import usePageButtonsData from './hooks'; +import messages from './messages'; + +jest.mock('data/redux/hooks', () => ({ + selectors: { + grades: { useGradeData: jest.fn() }, + }, + thunkActions: { + grades: { useFetchPrevNextGrades: jest.fn() }, + }, +})); + +const gradeData = { nextPage: 'test-next-page', prevPage: 'test-prev-page' }; +selectors.grades.useGradeData.mockReturnValue(gradeData); + +const fetchGrades = jest.fn(); +thunkActions.grades.useFetchPrevNextGrades.mockReturnValue(fetchGrades); + +let out; +describe('usePageButtonsData', () => { + beforeEach(() => { + jest.clearAllMocks(); + out = usePageButtonsData(); + }); + describe('behavior', () => { + it('initializes intl hook', () => { + expect(useIntl).toHaveBeenCalled(); + }); + it('initializes redux hooks', () => { + expect(selectors.grades.useGradeData).toHaveBeenCalled(); + expect(thunkActions.grades.useFetchPrevNextGrades).toHaveBeenCalled(); + }); + }); + describe('output', () => { + describe('prev button entry', () => { + it('is disabled iff prevPage is not provided', () => { + expect(out.prev.disabled).toEqual(false); + selectors.grades.useGradeData.mockReturnValueOnce({ + ...gradeData, + prevPage: undefined, + }); + out = usePageButtonsData(); + expect(out.prev.disabled).toEqual(true); + }); + it('calls fetch with prevPage on click', () => { + out.prev.onClick(); + expect(fetchGrades).toHaveBeenCalledWith(gradeData.prevPage); + }); + test('text display', () => { + expect(out.prev.text).toEqual(formatMessage(messages.prevPage)); + }); + }); + describe('next button entry', () => { + it('is disabled iff nextPage is not provided', () => { + expect(out.next.disabled).toEqual(false); + selectors.grades.useGradeData.mockReturnValueOnce({ + ...gradeData, + nextPage: undefined, + }); + out = usePageButtonsData(); + expect(out.next.disabled).toEqual(true); + }); + it('calls fetch with prevPage on click', () => { + out.next.onClick(); + expect(fetchGrades).toHaveBeenCalledWith(gradeData.nextPage); + }); + test('text display', () => { + expect(out.next.text).toEqual(formatMessage(messages.nextPage)); + }); + }); + }); +}); diff --git a/src/components/GradesView/PageButtons/index.jsx b/src/components/GradesView/PageButtons/index.jsx index 0c2fb1b..bb377f4 100644 --- a/src/components/GradesView/PageButtons/index.jsx +++ b/src/components/GradesView/PageButtons/index.jsx @@ -1,75 +1,37 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; import { Button } from '@edx/paragon'; -import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import selectors from 'data/selectors'; -import thunkActions from 'data/thunkActions'; -import messages from './messages'; +import usePageButtonsData from './hooks'; -export class PageButtons extends React.Component { - constructor(props) { - super(props); - this.getPrevGrades = this.getPrevGrades.bind(this); - this.getNextGrades = this.getNextGrades.bind(this); - } +export const PageButtons = () => { + const { prev, next } = usePageButtonsData(); - getPrevGrades() { - this.props.getPrevNextGrades(this.props.prevPage); - } - - getNextGrades() { - this.props.getPrevNextGrades(this.props.nextPage); - } - - render() { - return ( -
+ - -
- ); - } -} - -PageButtons.defaultProps = { - nextPage: '', - prevPage: '', + {prev.text} + + + + ); }; -PageButtons.propTypes = { - // redux - getPrevNextGrades: PropTypes.func.isRequired, - nextPage: PropTypes.string, - prevPage: PropTypes.string, -}; +PageButtons.propTypes = {}; -export const mapStateToProps = (state) => ({ - nextPage: selectors.grades.nextPage(state), - prevPage: selectors.grades.prevPage(state), -}); - -export const mapDispatchToProps = { - getPrevNextGrades: thunkActions.grades.fetchPrevNextGrades, -}; - -export default connect(mapStateToProps, mapDispatchToProps)(PageButtons); +export default PageButtons; diff --git a/src/components/GradesView/PageButtons/index.test.jsx b/src/components/GradesView/PageButtons/index.test.jsx new file mode 100644 index 0000000..3922408 --- /dev/null +++ b/src/components/GradesView/PageButtons/index.test.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { Button } from '@edx/paragon'; + +import usePageButtonsData from './hooks'; +import PageButtons from '.'; + +jest.mock('./hooks', () => jest.fn()); + +const hookProps = { + prev: { + disabled: 'prev-disabled', + onClick: jest.fn().mockName('hooks.prev.onClick'), + text: 'prev-text', + }, + next: { + disabled: 'next-disabled', + onClick: jest.fn().mockName('hooks.next.onClick'), + text: 'next-text', + }, +}; +usePageButtonsData.mockReturnValue(hookProps); + +let el; +describe('PageButtons component', () => { + beforeEach(() => { + jest.clearAllMocks(); + el = shallow(); + }); + describe('behavior', () => { + it('initializes component hooks', () => { + expect(usePageButtonsData).toHaveBeenCalled(); + }); + }); + describe('render', () => { + test('snapshot', () => { + expect(el).toMatchSnapshot(); + }); + test('prev button', () => { + const button = el.find(Button).at(0); + expect(button.props().disabled).toEqual(hookProps.prev.disabled); + expect(button.props().onClick).toEqual(hookProps.prev.onClick); + expect(button.text()).toEqual(hookProps.prev.text); + }); + test('next button', () => { + const button = el.find(Button).at(1); + expect(button.props().disabled).toEqual(hookProps.next.disabled); + expect(button.props().onClick).toEqual(hookProps.next.onClick); + expect(button.text()).toEqual(hookProps.next.text); + }); + }); +});