diff --git a/resources/assets/src/components/Pagination.tsx b/resources/assets/src/components/Pagination.tsx new file mode 100644 index 00000000..4b24e531 --- /dev/null +++ b/resources/assets/src/components/Pagination.tsx @@ -0,0 +1,95 @@ +import React from 'react' +import PaginationItem from './PaginationItem' + +interface Props { + page: number + totalPages: number + onChange(page: number): void | Promise +} + +export const labels = { + first: '«', + prev: '‹', + next: '›', + last: '»', +} + +const Pagination: React.FC = props => { + const { page, totalPages, onChange } = props + + return ( + + ) +} + +export default Pagination diff --git a/resources/assets/src/components/PaginationItem.tsx b/resources/assets/src/components/PaginationItem.tsx new file mode 100644 index 00000000..a240f89f --- /dev/null +++ b/resources/assets/src/components/PaginationItem.tsx @@ -0,0 +1,33 @@ +import React from 'react' + +interface Props { + disabled?: boolean + active?: boolean + onClick?(): void +} + +const PaginationItem: React.FC = props => { + const classes = ['page-item'] + if (props.active) { + classes.push('active') + } + if (props.disabled) { + classes.push('disabled') + } + + const handleClick = () => { + if (!props.disabled && props.onClick) { + props.onClick() + } + } + + return ( +
  • + + {props.children} + +
  • + ) +} + +export default PaginationItem diff --git a/resources/assets/tests/components/Pagination.test.tsx b/resources/assets/tests/components/Pagination.test.tsx new file mode 100644 index 00000000..39771699 --- /dev/null +++ b/resources/assets/tests/components/Pagination.test.tsx @@ -0,0 +1,177 @@ +import React from 'react' +import { render, fireEvent } from '@testing-library/react' +import Pagination, { labels } from '@/components/Pagination' + +describe('first page', () => { + it('enabled', () => { + const mock = jest.fn() + const { getByText } = render( + , + ) + fireEvent.click(getByText(labels.first)) + expect(mock).toBeCalledWith(1) + }) + + it('disabled', () => { + const mock = jest.fn() + const { getByText } = render( + , + ) + fireEvent.click(getByText(labels.first)) + expect(mock).not.toBeCalled() + }) +}) + +describe('last page', () => { + it('enabled', () => { + const mock = jest.fn() + const { getByText } = render( + , + ) + fireEvent.click(getByText(labels.last)) + expect(mock).toBeCalledWith(3) + }) + + it('disabled', () => { + const mock = jest.fn() + const { getByText } = render( + , + ) + fireEvent.click(getByText(labels.last)) + expect(mock).not.toBeCalled() + }) +}) + +describe('previous page', () => { + it('enabled', () => { + const mock = jest.fn() + const { getByText } = render( + , + ) + fireEvent.click(getByText(labels.prev)) + expect(mock).toBeCalledWith(2) + }) + + it('disabled', () => { + const mock = jest.fn() + const { getByText } = render( + , + ) + fireEvent.click(getByText(labels.prev)) + expect(mock).not.toBeCalled() + }) +}) + +describe('next page', () => { + it('enabled', () => { + const mock = jest.fn() + const { getByText } = render( + , + ) + fireEvent.click(getByText(labels.next)) + expect(mock).toBeCalledWith(4) + }) + + it('disabled', () => { + const mock = jest.fn() + const { getByText } = render( + , + ) + fireEvent.click(getByText(labels.next)) + expect(mock).not.toBeCalled() + }) +}) + +describe('middle pages', () => { + it('pages count less than 8', () => { + const mock = jest.fn() + const { getByText } = render( + , + ) + + fireEvent.click(getByText('1')) + expect(mock).toBeCalledWith(1) + + fireEvent.click(getByText('2')) + expect(mock).toBeCalledWith(2) + + fireEvent.click(getByText('3')) + expect(mock).toBeCalledWith(3) + + fireEvent.click(getByText('4')) + expect(mock).toBeCalledWith(4) + + fireEvent.click(getByText('5')) + expect(mock).toBeCalledWith(5) + + fireEvent.click(getByText('6')) + expect(mock).toBeCalledWith(6) + + fireEvent.click(getByText('7')) + expect(mock).toBeCalledWith(7) + }) + + describe('pages count greater than or equals to 8', () => { + it('edge', () => { + const mock = jest.fn() + const { getByText, queryByText } = render( + , + ) + + fireEvent.click(getByText('1')) + expect(mock).toBeCalledWith(1) + expect(queryByText('1')).toBeInTheDocument() + expect(queryByText('2')).toBeInTheDocument() + expect(queryByText('...')).toBeInTheDocument() + expect(queryByText('9')).toBeInTheDocument() + expect(queryByText('10')).toBeInTheDocument() + + fireEvent.click(getByText('2')) + expect(mock).toBeCalledWith(2) + expect(queryByText('1')).toBeInTheDocument() + expect(queryByText('2')).toBeInTheDocument() + expect(queryByText('...')).toBeInTheDocument() + expect(queryByText('9')).toBeInTheDocument() + expect(queryByText('10')).toBeInTheDocument() + + fireEvent.click(getByText('9')) + expect(mock).toBeCalledWith(9) + expect(queryByText('1')).toBeInTheDocument() + expect(queryByText('2')).toBeInTheDocument() + expect(queryByText('...')).toBeInTheDocument() + expect(queryByText('9')).toBeInTheDocument() + expect(queryByText('10')).toBeInTheDocument() + + fireEvent.click(getByText('10')) + expect(mock).toBeCalledWith(10) + expect(queryByText('1')).toBeInTheDocument() + expect(queryByText('2')).toBeInTheDocument() + expect(queryByText('...')).toBeInTheDocument() + expect(queryByText('9')).toBeInTheDocument() + expect(queryByText('10')).toBeInTheDocument() + }) + + it('middle', () => { + const mock = jest.fn() + const { getByText, queryByText, queryAllByText } = render( + , + ) + + expect(queryByText('1')).toBeInTheDocument() + expect(queryAllByText('...')).toHaveLength(2) + expect(queryByText('3')).toBeInTheDocument() + expect(queryByText('4')).toBeInTheDocument() + expect(queryByText('5')).toBeInTheDocument() + expect(queryByText('10')).toBeInTheDocument() + + fireEvent.click(getByText('5')) + expect(mock).toBeCalledWith(5) + + fireEvent.click(getByText('1')) + expect(mock).toBeCalledWith(1) + + fireEvent.click(getByText('10')) + expect(mock).toBeCalledWith(10) + }) + }) +})