From 0fbded82544e577acd5e575f4dfca48737c04e1b Mon Sep 17 00:00:00 2001 From: Viktor Rusakov Date: Mon, 16 Jan 2023 19:17:31 +0200 Subject: [PATCH] test: refactor Pagination tests --- src/DataTable/tests/TablePagination.test.jsx | 4 +- src/Pagination/DefaultPagination.jsx | 2 +- src/Pagination/Pagination.test.jsx | 346 +++++++++--------- src/Pagination/PaginationContext.jsx | 8 - .../subcomponents/PageOfCountButton.jsx | 4 +- .../subcomponents/PreviousPageButton.jsx | 3 +- 6 files changed, 186 insertions(+), 181 deletions(-) diff --git a/src/DataTable/tests/TablePagination.test.jsx b/src/DataTable/tests/TablePagination.test.jsx index 1da0c87073e..f7101d556f6 100644 --- a/src/DataTable/tests/TablePagination.test.jsx +++ b/src/DataTable/tests/TablePagination.test.jsx @@ -32,9 +32,9 @@ describe('', () => { const dropdownChoices = wrapper.find(Dropdown.Item); expect(dropdownChoices.length).toEqual(instance.pageCount); - const secondPageButton = dropdownChoices.at(1); + const secondPageButton = dropdownChoices.at(2); secondPageButton.simulate('click'); expect(gotoPageSpy).toHaveBeenCalledTimes(1); - expect(gotoPageSpy).toHaveBeenCalledWith(1); + expect(gotoPageSpy).toHaveBeenCalledWith(2); }); }); diff --git a/src/Pagination/DefaultPagination.jsx b/src/Pagination/DefaultPagination.jsx index 21643c9e640..12ceebf2d4e 100644 --- a/src/Pagination/DefaultPagination.jsx +++ b/src/Pagination/DefaultPagination.jsx @@ -38,4 +38,4 @@ function PaginationPages() { })} ); -} \ No newline at end of file +} diff --git a/src/Pagination/Pagination.test.jsx b/src/Pagination/Pagination.test.jsx index 78046cf4b53..6e257bee4a8 100644 --- a/src/Pagination/Pagination.test.jsx +++ b/src/Pagination/Pagination.test.jsx @@ -1,24 +1,33 @@ import React from 'react'; -import { mount } from 'enzyme'; import { Context as ResponsiveContext } from 'react-responsive'; +import renderer from 'react-test-renderer'; +import { + render, + act, + fireEvent, + screen, +} from '@testing-library/react'; +import '@testing-library/jest-dom'; import breakpoints from '../utils/breakpoints'; import Pagination from './index'; -import Dropdown from '../Dropdown'; +import { PAGINATION_VARIANTS, ELLIPSIS } from './constants'; const baseProps = { - state: { pageIndex: 1 }, + currentPage: 1, paginationLabel: 'pagination navigation', pageCount: 5, onPageSelect: () => {}, }; describe('', () => { - it('renders', () => { - const props = { - ...baseProps, - }; - const wrapper = mount(); - expect(wrapper.exists()).toEqual(true); + it('renders default variant', () => { + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); + }); + + it('renders with inverse colors', () => { + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); }); it('renders screen reader section', () => { @@ -29,46 +38,64 @@ describe('', () => { currentPage: 'Página actual', pageOfCount: 'de', }; + const expectedSrText = `${buttonLabels.page} 1, ${buttonLabels.currentPage}, ${buttonLabels.pageOfCount} ${baseProps.pageCount}`; const props = { ...baseProps, buttonLabels, }; - const wrapper = mount(); - expect( - wrapper.findWhere(node => node.hasClass('sr-only')).text(), - ).toEqual(`${buttonLabels.page} 1, ${buttonLabels.currentPage}, ${buttonLabels.pageOfCount} ${baseProps.pageCount}`); + render(); + const srText = screen.getByText(expectedSrText); + expect(srText).toHaveClass('sr-only'); }); - describe('handles currentPage props properly', () => { - it('overrides state currentPage when props currentPage changes', () => { - const initialPage = 1; - const newPage = 2; - const props = { - ...baseProps, - currentPage: initialPage, - }; - const wrapper = mount(); - expect(wrapper.state('currentPage')).toEqual(initialPage); - wrapper.setProps({ - currentPage: newPage, - }); - wrapper.update(); - expect(wrapper.state('currentPage')).toEqual(newPage); + it('correctly handles initial page prop', () => { + render(); + expect(screen.getByLabelText(/current page/i)).toHaveTextContent('3'); + }); + + it('renders ellipsis if there are too many pages', () => { + render(); + expect(screen.getByText(ELLIPSIS)).toBeInTheDocument(); + }); + + describe('handles controlled and uncontrolled behaviour properly', () => { + it('does not internally change page on page click if currentPage is provided', () => { + render(); + expect(screen.getByLabelText(/current page/i)).toHaveTextContent('1'); + + fireEvent.click(screen.getByText('Next')); + expect(screen.getByLabelText(/current page/i)).toHaveTextContent('1'); + + fireEvent.click(screen.getByRole('button', { name: 'Page 3' })); + expect(screen.getByLabelText(/current page/i)).toHaveTextContent('1'); }); - it('does not override state currentPage when props currentPage changes with existing value', () => { - const currentPage = 2; - const props = { - ...baseProps, - currentPage, - }; - const wrapper = mount(); - expect(wrapper.state('currentPage')).toEqual(currentPage); - wrapper.setProps({ - currentPage, - }); - wrapper.update(); - expect(wrapper.state('currentPage')).toEqual(currentPage); + it('controls page selection internally if currentPage is not provided', () => { + render(); + expect(screen.getByLabelText(/current page/i)).toHaveTextContent('1'); + + fireEvent.click(screen.getByText('Next')); + expect(screen.getByLabelText(/current page/i)).toHaveTextContent('2'); + + fireEvent.click(screen.getByRole('button', { name: 'Page 3' })); + expect(screen.getByLabelText(/current page/i)).toHaveTextContent('3'); + + fireEvent.click(screen.getByText('Previous')); + expect(screen.getByLabelText(/current page/i)).toHaveTextContent('2'); + }); + + it('does not chang page if you click "next" button while on last page', () => { + render(); + expect(screen.getByLabelText(/current page/i)).toHaveTextContent('5'); + fireEvent.click(screen.getByText('Next')); + expect(screen.getByLabelText(/current page/i)).toHaveTextContent('5'); + }); + + it('does not chang page if you click "previous" button while on first page', () => { + render(); + expect(screen.getByLabelText(/current page/i)).toHaveTextContent('1'); + fireEvent.click(screen.getByText('Previous')); + expect(screen.getByLabelText(/current page/i)).toHaveTextContent('1'); }); }); @@ -77,24 +104,28 @@ describe('', () => { const props = { ...baseProps, currentPage: 2, + buttonLabel: { + previous: 'Previous', + next: 'Next', + }, }; - const app = document.createElement('div'); - document.body.appendChild(app); - const wrapper = mount(, { attachTo: app }); - wrapper.find('button.previous').simulate('click'); - expect(wrapper.find('button.next').instance()).toEqual(document.activeElement); + render(); + fireEvent.click(screen.getByText('Previous')); + expect(screen.getByText('Next')).toHaveFocus(); }); it('should change focus to previous button if next page is last page', () => { const props = { ...baseProps, currentPage: baseProps.pageCount - 1, + buttonLabel: { + previous: 'Previous', + next: 'Next', + }, }; - const app = document.createElement('div'); - document.body.appendChild(app); - const wrapper = mount(, { attachTo: app }); - wrapper.find('button.next').simulate('click'); - expect(wrapper.find('button.previous').instance()).toEqual(document.activeElement); + render(); + fireEvent.click(screen.getByText('Next')); + expect(screen.getByText('Previous')).toHaveFocus(); }); }); @@ -105,35 +136,48 @@ describe('', () => { ...baseProps, paginationLabel, }; - const wrapper = mount(); - expect(wrapper.find('nav').prop('aria-label')).toEqual(paginationLabel); + render(); + expect(screen.getByRole('navigation')).toHaveAttribute('aria-label', paginationLabel); }); describe('should use correct number of pages', () => { it('should show 5 buttons on desktop', () => { - const wrapper = mount(( + render(( )); - expect(wrapper.findWhere((node) => { - const isPrevOrNext = node.hasClass('previous') || node.hasClass('next'); - return node.name() === 'button' && !isPrevOrNext; - })).toHaveLength(5); + + expect(screen.queryAllByRole('button', { name: /^Page/ })).toHaveLength(5); }); - it('should show 1 button on mobile', () => { + it('should show page of count text instead of pag buttons on mobile', () => { + const buttonLabels = { + previous: 'Anterior', + next: 'Siguiente', + page: 'Página', + currentPage: 'Página actual', + pageOfCount: 'de', + }; + const pageCount = 5; + const currentPage = 1; + const props = { + ...baseProps, + buttonLabels, + pageCount, + currentPage, + }; + // Use extra small window size to display the mobile version of `Pagination`. - const wrapper = mount(( + render(( - + )); - expect(wrapper.findWhere((node) => { - const name = node.name(); - const isPrevOrNext = node.hasClass('previous') || node.hasClass('next'); - return name === 'span' && node.hasClass('btn') && !isPrevOrNext; - })).toHaveLength(1); + + const pageOfCountLabel = `${buttonLabels.page} ${currentPage}, ${buttonLabels.currentPage}, ${buttonLabels.pageOfCount} ${pageCount}`; + expect(screen.queryAllByRole('button', { name: /^Page/ })).toHaveLength(0); + expect(screen.queryByLabelText(pageOfCountLabel)).toBeInTheDocument(); }); }); @@ -144,13 +188,13 @@ describe('', () => { ...baseProps, onPageSelect: spy, }; - const wrapper = mount(( + render(( )); - wrapper.find('.btn').at(1).simulate('click'); + fireEvent.click(screen.getByLabelText(/current page/i)); expect(spy).toHaveBeenCalledTimes(0); }); @@ -160,18 +204,16 @@ describe('', () => { ...baseProps, onPageSelect: spy, }; - const wrapper = mount(( + render(( )); - wrapper.find('.btn').at(2).simulate('click'); - expect(wrapper.state('currentPage')).toEqual(2); + fireEvent.click(screen.getByLabelText('Page 2')); expect(spy).toHaveBeenCalledTimes(1); - wrapper.find('.btn').at(3).simulate('click'); - expect(wrapper.state('currentPage')).toEqual(3); + fireEvent.click(screen.getByLabelText('Page 3')); expect(spy).toHaveBeenCalledTimes(2); }); }); @@ -183,11 +225,10 @@ describe('', () => { const props = { ...baseProps, onPageSelect: spy, + currentPage: 3, }; - const wrapper = mount(); - wrapper.setProps({ currentPage: 2 }); - wrapper.update(); - wrapper.find('button.previous').simulate('click'); + render(); + fireEvent.click(screen.getByRole('button', { name: 'Previous, Page 2' })); expect(spy).toHaveBeenCalledTimes(1); }); @@ -197,8 +238,8 @@ describe('', () => { ...baseProps, onPageSelect: spy, }; - const wrapper = mount(); - wrapper.find('button.next').simulate('click'); + render(); + fireEvent.click(screen.getByRole('button', { name: 'Next, Page 2' })); expect(spy).toHaveBeenCalledTimes(1); }); }); @@ -211,120 +252,91 @@ describe('', () => { currentPage: 'Página actual', pageOfCount: 'de', }; - - let wrapper; - let props; - /** - * made a proxy component because setProps can only be used with root component and - * Responsive Context Provider is needed to mock screen - * */ - // eslint-disable-next-line react/prop-types - function Proxy({ currentPage, width }) { - return ( - - - - ); - } - - beforeEach(() => { - props = { - ...baseProps, - buttonLabels, - }; - wrapper = mount( - , - ); - }); + const props = { + ...baseProps, + buttonLabels, + }; it('uses passed in previous button label', () => { - expect(wrapper.findWhere(node => ( - node.name() === 'button' && node.hasClass('previous') - )).prop('aria-label')).toEqual(buttonLabels.previous); - - wrapper.setProps({ currentPage: baseProps.pageCount }); - wrapper.update(); - - expect(wrapper.findWhere(node => ( - node.name() === 'button' && node.hasClass('previous') - )).prop('aria-label')).toEqual(`${buttonLabels.previous}, ${buttonLabels.page} 4`); + const { rerender } = render(); + // default label is used if we're on the first page + expect(screen.getByRole('button', { name: buttonLabels.previous })).toBeInTheDocument(); + + rerender(); + // label should change if we're not on the first page + const expectedPrevButtonAriaLabel = `${buttonLabels.previous}, ${buttonLabels.page} 4`; + expect(screen.getByRole('button', { name: expectedPrevButtonAriaLabel })).toBeInTheDocument(); }); it('uses passed in next button label', () => { - expect(wrapper.findWhere(node => ( - node.name() === 'button' && node.hasClass('next') - )).prop('aria-label')).toEqual(`${buttonLabels.next}, ${buttonLabels.page} 2`); - - wrapper.setProps({ currentPage: baseProps.pageCount }); - wrapper.update(); - - expect(wrapper.findWhere(node => ( - node.name() === 'button' && node.hasClass('next') - )).prop('aria-label')).toEqual(buttonLabels.next); + const { rerender } = render(); + // label should change if we're not on the last page + const expectedNextButtonAriaLabel = `${buttonLabels.next}, ${buttonLabels.page} 2`; + expect(screen.getByRole('button', { name: expectedNextButtonAriaLabel })).toBeInTheDocument(); + + rerender(); + // default label is used if we're on the last page + expect(screen.getByRole('button', { name: buttonLabels.next })).toBeInTheDocument(); }); it('uses passed in page button label', () => { - wrapper = mount(( + const currentPageLabel = `${buttonLabels.page} 1, ${buttonLabels.currentPage}`; + const pageLabel = `${buttonLabels.page} 1`; + + const { rerender } = render(( )); - expect(wrapper.state('currentPage')).toEqual(1); - expect(wrapper.find('.btn').at(1).prop('aria-label')) - .toEqual(`${buttonLabels.page} 1, ${buttonLabels.currentPage}`); - wrapper = mount(( + expect(screen.getByText('1')).toHaveAttribute('aria-label', currentPageLabel); + rerender(( )); + expect(screen.getByText('1')).toHaveAttribute('aria-label', pageLabel); - expect(wrapper.state('currentPage')).toEqual(2); - expect(wrapper.find('.btn').at(1).prop('aria-label')) - .toEqual(`${buttonLabels.page} 1`); - - wrapper = mount( - , - ); + rerender(( + + + + )); - expect(wrapper.find('.btn').at(1).prop('aria-label')) - .toEqual(`${buttonLabels.page} 1, ${buttonLabels.currentPage}, ${buttonLabels.pageOfCount} 5`); + const pageOfCountLabel = `${buttonLabels.page} 1, ${buttonLabels.currentPage}, ${buttonLabels.pageOfCount} 5`; + expect(screen.queryByLabelText(pageOfCountLabel)).toBeInTheDocument(); }); - it('for the reduced variant shows dropdown button with the page count as label', () => { - wrapper = mount(); + it('for the reduced variant shows dropdown button with the page count as label', async () => { + render(); - const dropdown = wrapper.find(Dropdown); - expect(dropdown.text()).toContain(`${baseProps.state.pageIndex} of ${baseProps.pageCount}`); + const dropdownLabel = `${baseProps.currentPage} de ${baseProps.pageCount}`; - const dropdownButton = wrapper.find('button.dropdown-toggle'); - dropdownButton.simulate('click'); - const dropdownChoices = wrapper.find(Dropdown.Item); - expect(dropdownChoices.length).toEqual(baseProps.pageCount); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: dropdownLabel })); + }); + expect(screen.queryAllByRole('button', { name: /^\d+$/ }).length).toEqual(baseProps.pageCount); }); it('renders only previous and next buttons in minimal variant', () => { - wrapper = mount(); - const items = wrapper.find('li.page-item'); - - expect(items.length).toEqual(2); + render(); + expect(screen.queryAllByRole('button').length).toEqual(2); }); - it('renders chevrons and buttons disabled when pageCount is 1 || 0 for all variants', () => { - const variantTypes = ['default', 'secondary', 'reduced', 'minimal']; - // default - variantTypes.forEach((variantType) => { - for (let i = 0; i < 3; i++) { - props = { - ...baseProps, - variant: variantType, - pageCount: i, - }; - wrapper = mount(); - const disabled = wrapper.find('button[disabled=true]'); - expect(props.pageCount).toEqual(i); - expect(disabled.length).toEqual(i === 2 ? 1 : 2); - } - }); - }); + test.each(Object.values(PAGINATION_VARIANTS))( + 'renders chevrons and buttons disabled when pageCount is 1 || 0 for %s variant', + (variant) => { + const { rerender } = render(); + expect(screen.getByRole('button', { name: /next/i })).toBeDisabled(); + expect(screen.getByRole('button', { name: /previous/i })).toBeDisabled(); + + rerender(); + expect(screen.getByRole('button', { name: /next/i })).toBeDisabled(); + expect(screen.getByRole('button', { name: /previous/i })).toBeDisabled(); + + rerender(); + expect(screen.getByRole('button', { name: /next/i })).not.toBeDisabled(); + expect(screen.getByRole('button', { name: /previous/i })).toBeDisabled(); + }, + ); }); }); diff --git a/src/Pagination/PaginationContext.jsx b/src/Pagination/PaginationContext.jsx index ed34a1e3ec0..9638e4be3f0 100644 --- a/src/Pagination/PaginationContext.jsx +++ b/src/Pagination/PaginationContext.jsx @@ -53,9 +53,6 @@ function PaginationContextProvider({ }; const handlePreviousButtonClick = () => { - if (isOnFirstPage()) { - return; - } onPageSelect(currentPage - 1); if (currentPage === 2) { nextButtonRef.current.focus(); @@ -66,9 +63,6 @@ function PaginationContextProvider({ }; const handleNextButtonClick = () => { - if (isOnLastPage()) { - return; - } onPageSelect(currentPage + 1); if (currentPage === pageCount - 1) { previousButtonRef.current.focus(); @@ -109,7 +103,6 @@ function PaginationContextProvider({ }; const getAriaLabelForPageOfCountButton = () => `${buttonLabels.page} ${currentPage}, ${buttonLabels.currentPage}, ${buttonLabels.pageOfCount} ${pageCount}`; - const getLabelForPageOfCountButton = () => currentPage; const getScreenReaderText = () => `${buttonLabels.page} ${currentPage}, ${buttonLabels.currentPage}, ${buttonLabels.pageOfCount} ${pageCount}`; const getPageOfText = () => `${currentPage} ${buttonLabels.pageOfCount} ${pageCount}`; @@ -148,7 +141,6 @@ function PaginationContextProvider({ getAriaLabelForPageButton, getAriaLabelForPreviousButton, getAriaLabelForPageOfCountButton, - getLabelForPageOfCountButton, getPageButtonVariant, handlePreviousButtonClick, handleNextButtonClick, diff --git a/src/Pagination/subcomponents/PageOfCountButton.jsx b/src/Pagination/subcomponents/PageOfCountButton.jsx index 0b96f074a81..022bd82db6c 100644 --- a/src/Pagination/subcomponents/PageOfCountButton.jsx +++ b/src/Pagination/subcomponents/PageOfCountButton.jsx @@ -3,10 +3,10 @@ import classNames from 'classnames'; import PaginationContext from '../PaginationContext'; export default function PageOfCountButton() { - const { getAriaLabelForPageOfCountButton, getLabelForPageOfCountButton } = useContext(PaginationContext); + const { getAriaLabelForPageOfCountButton, getPageOfText } = useContext(PaginationContext); const ariaLabel = getAriaLabelForPageOfCountButton(); - const label = getLabelForPageOfCountButton(); + const label = getPageOfText(); return (
  • diff --git a/src/Pagination/subcomponents/PreviousPageButton.jsx b/src/Pagination/subcomponents/PreviousPageButton.jsx index c2a34e0ff39..d0ab0f11bd9 100644 --- a/src/Pagination/subcomponents/PreviousPageButton.jsx +++ b/src/Pagination/subcomponents/PreviousPageButton.jsx @@ -9,6 +9,7 @@ import PaginationContext from '../PaginationContext'; export default function PreviousPageButton() { const { invertColors, + getPageButtonVariant, isDefaultVariant, isOnFirstPage, getAriaLabelForPreviousButton, @@ -26,7 +27,7 @@ export default function PreviousPageButton() {