import userEvent from '@testing-library/user-event'
import React, { useContext, useState } from 'react'
import { MarginProps } from 'styled-system'
import renderWithTheme from '../../tests/helpers/renderWithTheme'
import { ScreenSizeContext, SIZES } from '../ScreenSizeProvider/ScreenSizeProvider'
import SplitButton from '../SplitButton/SplitButton'
import DataGrid from './DataGrid'
const TEST_DATA = [
{
name: 'Config 1',
created: '4/13/2020',
active: true,
},
{
name: 'Config 2',
created: '5/25/2019',
active: true,
},
{
name: 'Config 3',
created: '8/16/2019',
active: false,
},
{
name: 'Config 4',
created: '2/20/2019',
active: true,
},
{
name: 'Config 5',
created: '5/16/2019',
active: false,
},
{
name: 'Config 6',
created: '10/31/2019',
active: true,
},
{
name: 'Config 7',
created: '11/23/2019',
active: true,
},
{
name: 'Config 8',
created: '3/20/2020',
active: false,
},
{
name: 'Config 9',
created: '6/13/2020',
active: false,
},
{
name: 'Config 10',
created: '7/1/2020',
active: true,
},
{
name: 'Config 11',
created: '3/3/2020',
active: true,
},
]
const BoolComponent = ({ value }: { value: boolean }): React.ReactElement => {
return
{value ? 'YES' : 'NO'}
}
interface Datum {
name: string
created: string
active: boolean
}
interface ContainerProps extends MarginProps {
providePageSizeOptions?: boolean
providePageCount?: boolean
showResultsCount?: boolean
fullWidth?: boolean
sticky?: 'none' | 'right'
}
const DataGridContainer = (props: ContainerProps): React.ReactElement => {
const {
providePageSizeOptions = true,
providePageCount = true,
showResultsCount = true,
fullWidth,
sticky,
...rest
} = props
const size = useContext(ScreenSizeContext)
const isCardView = size <= SIZES['tiny']
const [data, setData] = useState<{ [key: string]: any }[]>(TEST_DATA)
const [sortOptions, setSortOptions] = useState<{ id: string; sortAscending: boolean }[]>([
{ id: 'created', sortAscending: false },
])
const [paginationOptions, setPaginationOptions] = useState<{
currentPage: number
pageSize: number
pageCount?: number
pageSizeOptions?: number[]
}>({ currentPage: 1, pageCount: 3, pageSize: 4, pageSizeOptions: [4, 6, 12] })
const clone = (rowData: { [key: string]: any }): void => {
setData((currentData): { [key: string]: any }[] => [
...currentData,
{ ...rowData, name: `${rowData.name} Copy` },
])
}
const deleteRow = (rowData: { [key: string]: any }): void => {
const deleteIndex = data.findIndex((datum): boolean => datum.name === rowData.name)
setData((currentData): { [key: string]: any }[] => {
const newData = [...currentData]
newData.splice(deleteIndex, 1)
return newData
})
}
const onSort = (newSortOptions: { id: string; sortAscending: boolean }[]): void => {
const sortId = newSortOptions[0].id
const sortAscending = newSortOptions[0].sortAscending
const dataCopy = JSON.parse(JSON.stringify(data))
if (sortId === 'created') {
if (sortAscending) {
dataCopy.sort((a: Datum, b: Datum): number => {
return (new Date(a.created) as any) - (new Date(b.created) as any)
})
} else {
dataCopy.sort((a: Datum, b: Datum): number => {
return (new Date(b.created) as any) - (new Date(a.created) as any)
})
}
} else if (sortId === 'active') {
if (sortAscending) {
dataCopy.sort((a: Datum, b: Datum): number => {
return a.active === b.active ? 0 : a.active ? 1 : -1
})
} else {
dataCopy.sort((a: Datum, b: Datum): number => {
return a.active === b.active ? 0 : a.active ? -1 : 1
})
}
}
setData(dataCopy)
setSortOptions(newSortOptions)
}
const paginateData = (): { [key: string]: any }[] => {
const index1 = (paginationOptions.currentPage - 1) * paginationOptions.pageSize
const index2 = index1 + paginationOptions.pageSize
return data.slice(index1, index2)
}
const onPageChange = (newPaginationOptions: {
currentPage: number
pageSize: number
pageCount?: number
}): void => {
if (newPaginationOptions.pageSize !== paginationOptions.pageSize) {
newPaginationOptions.pageCount = Math.ceil(data.length / newPaginationOptions.pageSize)
}
setPaginationOptions(newPaginationOptions)
}
const getResultsCountText = (): string => {
if (paginationOptions.pageCount) {
return `Showing ${(paginationOptions.currentPage - 1) * paginationOptions.pageSize + 1} to ${
paginationOptions.currentPage * paginationOptions.pageSize
} of ${data.length}`
} else {
return `Showing ${(paginationOptions.currentPage - 1) * paginationOptions.pageSize + 1} to ${
paginationOptions.currentPage * paginationOptions.pageSize
}`
}
}
const getPaginationOptions = (): {
currentPage: number
pageSize: number
pageCount?: number
pageSizeOptions?: number[]
} => {
const paginationOptsCopy = JSON.parse(JSON.stringify(paginationOptions))
if (!providePageSizeOptions) {
paginationOptsCopy.pageSizeOptions = undefined
}
if (!providePageCount) {
paginationOptsCopy.pageCount = undefined
}
return paginationOptsCopy
}
return (
{showResultsCount && !isCardView && {getResultsCountText()}}
{providePageSizeOptions && }
{(rowData): React.ReactElement => (
{
return
}}
mainActionLabel="Edit"
>
{
clone(rowData)
}}
>
Clone
deleteRow(rowData)}>
Delete
)}
{isCardView && showResultsCount && size.toString() !== 'tiny' ? (
{getResultsCountText()}
) : null}
{providePageCount ? (
) : (
)}
{isCardView && showResultsCount && size.toString() === 'tiny' ? (
{getResultsCountText()}
) : null}
)
}
describe('component: DataGrid', () => {
test('should be able to change page size', () => {
const { getByLabelText, getByText } = renderWithTheme()
const sixItemsButton = getByLabelText('View 6 rows per page')
userEvent.click(sixItemsButton)
expect(getByText('Showing 1 to 6 of 11')).toBeInTheDocument()
})
test('should be able to change sort direction', () => {
const { getByText } = renderWithTheme()
const createdHeader = getByText('Created').parentElement as HTMLElement
userEvent.click(createdHeader)
expect(createdHeader.parentElement).toHaveAttribute('aria-sort', 'ascending')
})
test('should render using as prop', () => {
const { getAllByText } = renderWithTheme()
expect(getAllByText('YES')[0]).toBeInTheDocument()
expect(getAllByText('NO')[0]).toBeInTheDocument()
})
test('should be able to change the page using Pagination', () => {
const { getByText, getByLabelText } = renderWithTheme()
const page2Button = getByLabelText('Go to page 2')
userEvent.click(page2Button)
// First result on page 2
expect(getByText('Config 5')).toBeInTheDocument()
// Last result on page 2
expect(getByText('Config 8')).toBeInTheDocument()
})
test('should be able to change the page using PrevNext', () => {
const { getByText } = renderWithTheme()
const nextPageButton = getByText('Next')
userEvent.click(nextPageButton)
// First result on page 2
expect(getByText('Config 5')).toBeInTheDocument()
// Last result on page 2
expect(getByText('Config 8')).toBeInTheDocument()
})
test('should render content from Column component', () => {
const { getAllByText } = renderWithTheme()
expect(getAllByText('Edit')[0]).toBeInTheDocument()
})
test('Should support margin props', () => {
const { getByTestId } = renderWithTheme(
)
const dataGrid = getByTestId('data-grid')
expect(dataGrid).toHaveStyle('margin-top: 4px')
expect(dataGrid).toHaveStyle('margin-bottom: 100px')
expect(dataGrid).toHaveStyle('margin-left: 40px')
expect(dataGrid).toHaveStyle('margin-right: 40px')
})
test('Should support setting position of right column as sticky', () => {
const { getAllByTestId, rerender } = renderWithTheme()
getAllByTestId('last-col').forEach((element) => {
expect(element).toHaveStyle('position: sticky')
})
rerender()
getAllByTestId('last-col').forEach((element) => {
expect(element).not.toHaveStyle('position: sticky')
})
})
test('should allow uncontrolled pagisort', () => {
const onSort = jest.fn()
const onPageRoot = jest.fn()
const onPageLeaf = jest.fn()
const onPagisort = jest.fn()
const data: Record[] = Array(2).fill({ hi: 'there', hello: '!' })
const { getByLabelText, getByRole } = renderWithTheme(
)
// Sort descending
userEvent.click(getByRole('button', { name: 'Hi' }))
expect(onSort).toHaveBeenCalledTimes(1)
expect(onSort).toHaveBeenLastCalledWith([{ id: 'hi', sortAscending: false }])
expect(onPagisort).toHaveBeenCalledTimes(1)
expect(onPagisort).toHaveBeenLastCalledWith({
currentPage: 1,
pageSize: 2,
itemCount: 3,
itemOffset: 0,
pageCount: 2,
sort: [{ id: 'hi', sortAscending: false }],
})
expect(onPageRoot).toHaveBeenCalledTimes(0)
expect(onPageLeaf).toHaveBeenCalledTimes(0)
// Page change
userEvent.click(getByLabelText('Go to page 2'))
expect(onSort).toHaveBeenCalledTimes(1)
expect(onPagisort).toHaveBeenCalledTimes(2)
expect(onPagisort).toHaveBeenLastCalledWith({
currentPage: 2,
pageSize: 2,
itemCount: 3,
itemOffset: 2,
pageCount: 2,
sort: [{ id: 'hi', sortAscending: false }],
})
expect(onPageRoot).toHaveBeenCalledTimes(1)
expect(onPageRoot).toHaveBeenLastCalledWith({
currentPage: 2,
pageSize: 2,
itemCount: 3,
itemOffset: 2,
pageCount: 2,
})
expect(onPageLeaf).toHaveBeenCalledTimes(1)
expect(onPageLeaf).toHaveBeenLastCalledWith(2)
// Sort ascending
userEvent.click(getByRole('button', { name: 'Hi' }))
expect(onSort).toHaveBeenCalledTimes(2)
expect(onSort).toHaveBeenLastCalledWith([{ id: 'hi', sortAscending: true }])
expect(onPagisort).toHaveBeenCalledTimes(3)
expect(onPagisort).toHaveBeenLastCalledWith({
currentPage: 2,
pageSize: 2,
itemCount: 3,
itemOffset: 2,
pageCount: 2,
sort: [{ id: 'hi', sortAscending: true }],
})
expect(onPageRoot).toHaveBeenCalledTimes(1)
expect(onPageLeaf).toHaveBeenCalledTimes(1)
// Page size change
userEvent.click(getByLabelText('View 5 rows per page'))
expect(onSort).toHaveBeenCalledTimes(2)
expect(onPagisort).toHaveBeenCalledTimes(4)
// Note that itemOffset & currentPage changed, as they were no longer valid.
expect(onPagisort).toHaveBeenLastCalledWith({
currentPage: 1,
pageSize: 5,
itemCount: 3,
itemOffset: 0,
pageCount: 1,
sort: [{ id: 'hi', sortAscending: true }],
})
expect(onPageRoot).toHaveBeenCalledTimes(2)
expect(onPageRoot).toHaveBeenLastCalledWith({
currentPage: 1,
pageSize: 5,
itemCount: 3,
itemOffset: 0,
pageCount: 1,
})
expect(onPageLeaf).toHaveBeenCalledTimes(1)
})
test('should allow controlled pagisort at root', () => {
const data: Record[] = Array(2).fill({ hi: 'there', hello: '!' })
const { container, getByLabelText, rerender } = renderWithTheme(
)
const sortHeader = container.querySelector('[aria-sort]')
expect(sortHeader).toHaveTextContent('Hi')
expect(sortHeader).toHaveAttribute('aria-sort', 'ascending')
expect(getByLabelText('View 2 rows per page')).toHaveAttribute('aria-selected', 'true')
expect(getByLabelText('Current page, 2')).toHaveAttribute('aria-current', 'page')
// For now `currentPage` is required, should be removed later...
rerender(
)
expect(sortHeader).toHaveAttribute('aria-sort', 'descending')
expect(getByLabelText('View 5 rows per page')).toHaveAttribute('aria-selected', 'true')
expect(getByLabelText('Current page, 3')).toHaveAttribute('aria-current', 'page')
})
test('should allow controlled pagisort at leaf', () => {
const data: Record[] = Array(2).fill({ hi: 'there', hello: '!' })
const { getByLabelText, rerender } = renderWithTheme(
)
expect(getByLabelText('View 2 rows per page')).toHaveAttribute('aria-selected', 'true')
expect(getByLabelText('Current page, 2')).toHaveAttribute('aria-current', 'page')
rerender(
)
expect(getByLabelText('View 5 rows per page')).toHaveAttribute('aria-selected', 'true')
expect(getByLabelText('Current page, 3')).toHaveAttribute('aria-current', 'page')
})
})