add pagination component

This commit is contained in:
Pig Fang 2020-02-10 15:59:56 +08:00
parent 576a4c442f
commit 89055fe192
3 changed files with 305 additions and 0 deletions

View File

@ -0,0 +1,95 @@
import React from 'react'
import PaginationItem from './PaginationItem'
interface Props {
page: number
totalPages: number
onChange(page: number): void | Promise<void>
}
export const labels = {
first: '«',
prev: '',
next: '',
last: '»',
}
const Pagination: React.FC<Props> = props => {
const { page, totalPages, onChange } = props
return (
<ul className="pagination">
<PaginationItem disabled={page === 1} onClick={() => onChange(1)}>
{labels.first}
</PaginationItem>
<PaginationItem disabled={page === 1} onClick={() => onChange(page - 1)}>
{labels.prev}
</PaginationItem>
{totalPages < 8 ? (
Array.from({ length: totalPages }).map((_, i) => (
<PaginationItem
key={i}
active={page === i + 1}
onClick={() => onChange(i + 1)}
>
{i + 1}
</PaginationItem>
))
) : page < 3 || totalPages - page < 2 ? (
<>
{[1, 2].map(p => (
<PaginationItem
key={p}
active={page === p}
onClick={() => onChange(p)}
>
{p}
</PaginationItem>
))}
<PaginationItem disabled>...</PaginationItem>
{[totalPages - 1, totalPages].map(p => (
<PaginationItem
key={p}
active={page === p}
onClick={() => onChange(p)}
>
{p}
</PaginationItem>
))}
</>
) : (
<>
<PaginationItem onClick={() => onChange(1)}>1</PaginationItem>
<PaginationItem disabled>...</PaginationItem>
{[page - 1, page, page + 1].map(p => (
<PaginationItem
key={p}
active={page === p}
onClick={() => onChange(p)}
>
{p}
</PaginationItem>
))}
<PaginationItem disabled>...</PaginationItem>
<PaginationItem onClick={() => onChange(totalPages)}>
{totalPages}
</PaginationItem>
</>
)}
<PaginationItem
disabled={page === totalPages}
onClick={() => onChange(page + 1)}
>
{labels.next}
</PaginationItem>
<PaginationItem
disabled={page === totalPages}
onClick={() => onChange(totalPages)}
>
{labels.last}
</PaginationItem>
</ul>
)
}
export default Pagination

View File

@ -0,0 +1,33 @@
import React from 'react'
interface Props {
disabled?: boolean
active?: boolean
onClick?(): void
}
const PaginationItem: React.FC<Props> = 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 (
<li className={classes.join(' ')} onClick={handleClick}>
<a href="#" className="page-link" aria-disabled={props.disabled}>
{props.children}
</a>
</li>
)
}
export default PaginationItem

View File

@ -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(
<Pagination page={2} totalPages={3} onChange={mock} />,
)
fireEvent.click(getByText(labels.first))
expect(mock).toBeCalledWith(1)
})
it('disabled', () => {
const mock = jest.fn()
const { getByText } = render(
<Pagination page={1} totalPages={3} onChange={mock} />,
)
fireEvent.click(getByText(labels.first))
expect(mock).not.toBeCalled()
})
})
describe('last page', () => {
it('enabled', () => {
const mock = jest.fn()
const { getByText } = render(
<Pagination page={2} totalPages={3} onChange={mock} />,
)
fireEvent.click(getByText(labels.last))
expect(mock).toBeCalledWith(3)
})
it('disabled', () => {
const mock = jest.fn()
const { getByText } = render(
<Pagination page={3} totalPages={3} onChange={mock} />,
)
fireEvent.click(getByText(labels.last))
expect(mock).not.toBeCalled()
})
})
describe('previous page', () => {
it('enabled', () => {
const mock = jest.fn()
const { getByText } = render(
<Pagination page={3} totalPages={5} onChange={mock} />,
)
fireEvent.click(getByText(labels.prev))
expect(mock).toBeCalledWith(2)
})
it('disabled', () => {
const mock = jest.fn()
const { getByText } = render(
<Pagination page={1} totalPages={5} onChange={mock} />,
)
fireEvent.click(getByText(labels.prev))
expect(mock).not.toBeCalled()
})
})
describe('next page', () => {
it('enabled', () => {
const mock = jest.fn()
const { getByText } = render(
<Pagination page={3} totalPages={5} onChange={mock} />,
)
fireEvent.click(getByText(labels.next))
expect(mock).toBeCalledWith(4)
})
it('disabled', () => {
const mock = jest.fn()
const { getByText } = render(
<Pagination page={5} totalPages={5} onChange={mock} />,
)
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(
<Pagination page={1} totalPages={7} onChange={mock} />,
)
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(
<Pagination page={2} totalPages={10} onChange={mock} />,
)
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(
<Pagination page={4} totalPages={10} onChange={mock} />,
)
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)
})
})
})