refactor: PageButtons component modernization

This commit is contained in:
Ben Warzeski
2023-05-11 13:29:14 -04:00
parent 4cf5ba7a07
commit 6b149e9ce0
7 changed files with 228 additions and 294 deletions

View File

@@ -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(<PageButtons {...props} />);
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(<PageButtons {...props} />);
});
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);
});
});
});

View File

@@ -1,133 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PageButtons component snapshots buttons enabled with both endpoints provided 1`] = `
<div
className="d-flex justify-content-center"
style={
Object {
"paddingBottom": "20px",
}
}
>
<Button
disabled={false}
onClick={[Function]}
style={
Object {
"margin": "20px",
}
}
variant="outline-primary"
>
<FormattedMessage
defaultMessage="Previous Page"
description="Grades tab Previous Page button text"
id="gradebook.GradesView.PageButtons.prevPage"
/>
</Button>
<Button
disabled={false}
onClick={[Function]}
style={
Object {
"margin": "20px",
}
}
variant="outline-primary"
>
<FormattedMessage
defaultMessage="Next Page"
description="Grades tab Next Page button text"
id="gradebook.GradesView.PageButtons.nextPage"
/>
</Button>
</div>
`;
exports[`PageButtons component snapshots nextPage disabled if not provided 1`] = `
<div
className="d-flex justify-content-center"
style={
Object {
"paddingBottom": "20px",
}
}
>
<Button
disabled={false}
onClick={[Function]}
style={
Object {
"margin": "20px",
}
}
variant="outline-primary"
>
<FormattedMessage
defaultMessage="Previous Page"
description="Grades tab Previous Page button text"
id="gradebook.GradesView.PageButtons.prevPage"
/>
</Button>
<Button
disabled={true}
onClick={[Function]}
style={
Object {
"margin": "20px",
}
}
variant="outline-primary"
>
<FormattedMessage
defaultMessage="Next Page"
description="Grades tab Next Page button text"
id="gradebook.GradesView.PageButtons.nextPage"
/>
</Button>
</div>
`;
exports[`PageButtons component snapshots prevPage disabled if not provided 1`] = `
<div
className="d-flex justify-content-center"
style={
Object {
"paddingBottom": "20px",
}
}
>
<Button
disabled={true}
onClick={[Function]}
style={
Object {
"margin": "20px",
}
}
variant="outline-primary"
>
<FormattedMessage
defaultMessage="Previous Page"
description="Grades tab Previous Page button text"
id="gradebook.GradesView.PageButtons.prevPage"
/>
</Button>
<Button
disabled={false}
onClick={[Function]}
style={
Object {
"margin": "20px",
}
}
variant="outline-primary"
>
<FormattedMessage
defaultMessage="Next Page"
description="Grades tab Next Page button text"
id="gradebook.GradesView.PageButtons.nextPage"
/>
</Button>
</div>
`;

View File

@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PageButtons component render snapshot 1`] = `
<div
className="d-flex justify-content-center"
style={
Object {
"paddingBottom": "20px",
}
}
>
<Button
disabled="prev-disabled"
onClick={[MockFunction hooks.prev.onClick]}
style={
Object {
"margin": "20px",
}
}
variant="outline-primary"
>
prev-text
</Button>
<Button
disabled="next-disabled"
onClick={[MockFunction hooks.next.onClick]}
style={
Object {
"margin": "20px",
}
}
variant="outline-primary"
>
next-text
</Button>
</div>
`;

View File

@@ -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;

View File

@@ -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));
});
});
});
});

View File

@@ -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 (
<div
className="d-flex justify-content-center"
style={{ paddingBottom: '20px' }}
return (
<div
className="d-flex justify-content-center"
style={{ paddingBottom: '20px' }}
>
<Button
style={{ margin: '20px' }}
variant="outline-primary"
disabled={prev.disabled}
onClick={prev.onClick}
>
<Button
style={{ margin: '20px' }}
variant="outline-primary"
disabled={!this.props.prevPage}
onClick={this.getPrevGrades}
>
<FormattedMessage {...messages.prevPage} />
</Button>
<Button
style={{ margin: '20px' }}
variant="outline-primary"
disabled={!this.props.nextPage}
onClick={this.getNextGrades}
>
<FormattedMessage {...messages.nextPage} />
</Button>
</div>
);
}
}
PageButtons.defaultProps = {
nextPage: '',
prevPage: '',
{prev.text}
</Button>
<Button
style={{ margin: '20px' }}
variant="outline-primary"
disabled={next.disabled}
onClick={next.onClick}
>
{next.text}
</Button>
</div>
);
};
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;

View File

@@ -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(<PageButtons />);
});
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);
});
});
});