-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #128 from fhlavac/sort
- Loading branch information
Showing
6 changed files
with
400 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* eslint-disable no-nested-ternary */ | ||
import React from 'react'; | ||
import { useDataViewSort } from '@patternfly/react-data-view/dist/dynamic/Hooks'; | ||
import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; | ||
import { BrowserRouter, useSearchParams } from 'react-router-dom'; | ||
import { ThProps } from '@patternfly/react-table'; | ||
|
||
interface Repository { | ||
name: string; | ||
branches: string; | ||
prs: string; | ||
workspaces: string; | ||
lastCommit: string; | ||
} | ||
|
||
const COLUMNS = [ | ||
{ label: 'Repository', key: 'name', index: 0 }, | ||
{ label: 'Branch', key: 'branches', index: 1 }, | ||
{ label: 'Pull request', key: 'prs', index: 2 }, | ||
{ label: 'Workspace', key: 'workspaces', index: 3 }, | ||
{ label: 'Last commit', key: 'lastCommit', index: 4 }, | ||
]; | ||
|
||
const repositories: Repository[] = [ | ||
{ name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: '2023-11-01' }, | ||
{ name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: '2023-11-06' }, | ||
{ name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: '2023-11-02' }, | ||
{ name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: '2023-11-05' }, | ||
{ name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: '2023-11-03' }, | ||
{ name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: '2023-11-04' }, | ||
]; | ||
|
||
const sortData = (data: Repository[], sortBy: keyof Repository | undefined, direction: 'asc' | 'desc' | undefined) => | ||
sortBy && direction | ||
? [ ...data ].sort((a, b) => | ||
direction === 'asc' | ||
? a[sortBy] < b[sortBy] ? -1 : a[sortBy] > b[sortBy] ? 1 : 0 | ||
: a[sortBy] > b[sortBy] ? -1 : a[sortBy] < b[sortBy] ? 1 : 0 | ||
) | ||
: data; | ||
|
||
const TestTable: React.FunctionComponent = () => { | ||
const [ searchParams, setSearchParams ] = useSearchParams(); | ||
const { sortBy, direction, onSort } = useDataViewSort({ searchParams, setSearchParams }); | ||
const sortByIndex = React.useMemo(() => COLUMNS.findIndex(item => item.key === sortBy), [ sortBy ]); | ||
|
||
const getSortParams = (columnIndex: number): ThProps['sort'] => ({ | ||
sortBy: { | ||
index: sortByIndex, | ||
direction, | ||
defaultDirection: 'asc', | ||
}, | ||
onSort: (_event, index, direction) => onSort(_event, COLUMNS[index].key, direction), | ||
columnIndex, | ||
}); | ||
|
||
const columns: DataViewTh[] = COLUMNS.map((column, index) => ({ | ||
cell: column.label, | ||
props: { sort: getSortParams(index) }, | ||
})); | ||
|
||
const rows: DataViewTr[] = React.useMemo( | ||
() => | ||
sortData(repositories, sortBy ? sortBy as keyof Repository : undefined, direction).map(({ name, branches, prs, workspaces, lastCommit }) => [ | ||
name, | ||
branches, | ||
prs, | ||
workspaces, | ||
lastCommit, | ||
]), | ||
[ sortBy, direction ] | ||
); | ||
|
||
return <DataViewTable aria-label="Repositories table" ouiaId="test-table" columns={columns} rows={rows} />; | ||
}; | ||
|
||
describe('DataViewTable Sorting with Hook', () => { | ||
it('sorts by repository name in ascending and descending order', () => { | ||
cy.mount( | ||
<BrowserRouter> | ||
<TestTable /> | ||
</BrowserRouter> | ||
); | ||
|
||
cy.get('[data-ouia-component-id="test-table-th-0"]').click(); | ||
cy.get('[data-ouia-component-id="test-table-td-0-0"]').should('contain', 'Repository five'); | ||
cy.get('[data-ouia-component-id="test-table-td-5-0"]').should('contain', 'Repository two'); | ||
|
||
cy.get('[data-ouia-component-id="test-table-th-0"]').click(); | ||
cy.get('[data-ouia-component-id="test-table-td-0-0"]').should('contain', 'Repository two'); | ||
cy.get('[data-ouia-component-id="test-table-td-5-0"]').should('contain', 'Repository five'); | ||
}); | ||
|
||
it('sorts by last commit date in ascending and descending order', () => { | ||
cy.mount( | ||
<BrowserRouter> | ||
<TestTable /> | ||
</BrowserRouter> | ||
); | ||
|
||
cy.get('[data-ouia-component-id="test-table-th-4"]').click(); | ||
cy.get('[data-ouia-component-id="test-table-td-0-4"]').should('contain', '2023-11-01'); | ||
cy.get('[data-ouia-component-id="test-table-td-5-4"]').should('contain', '2023-11-06'); | ||
|
||
cy.get('[data-ouia-component-id="test-table-th-4"]').click(); | ||
cy.get('[data-ouia-component-id="test-table-td-0-4"]').should('contain', '2023-11-06'); | ||
cy.get('[data-ouia-component-id="test-table-td-5-4"]').should('contain', '2023-11-01'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
...le/patternfly-docs/content/extensions/data-view/examples/Functionality/SortingExample.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/* eslint-disable no-nested-ternary */ | ||
import React, { useMemo } from 'react'; | ||
import { useDataViewSort } from '@patternfly/react-data-view/dist/dynamic/Hooks'; | ||
import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable'; | ||
import { ThProps } from '@patternfly/react-table'; | ||
import { BrowserRouter, useSearchParams } from 'react-router-dom'; | ||
|
||
interface Repository { | ||
name: string; | ||
branches: string; | ||
prs: string; | ||
workspaces: string; | ||
lastCommit: string; | ||
}; | ||
|
||
const COLUMNS = [ | ||
{ label: 'Repository', key: 'name', index: 0 }, | ||
{ label: 'Branch', key: 'branches', index: 1 }, | ||
{ label: 'Pull request', key: 'prs', index: 2 }, | ||
{ label: 'Workspace', key: 'workspaces', index: 3 }, | ||
{ label: 'Last commit', key: 'lastCommit', index: 4 } | ||
]; | ||
|
||
const repositories: Repository[] = [ | ||
{ name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' }, | ||
{ name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' }, | ||
{ name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' }, | ||
{ name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' }, | ||
{ name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' }, | ||
{ name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' } | ||
]; | ||
|
||
const sortData = (data: Repository[], sortBy: string | undefined, direction: 'asc' | 'desc' | undefined) => | ||
sortBy && direction | ||
? [ ...data ].sort((a, b) => | ||
direction === 'asc' | ||
? a[sortBy] < b[sortBy] ? -1 : a[sortBy] > b[sortBy] ? 1 : 0 | ||
: a[sortBy] > b[sortBy] ? -1 : a[sortBy] < b[sortBy] ? 1 : 0 | ||
) | ||
: data; | ||
|
||
const ouiaId = 'TableExample'; | ||
|
||
export const MyTable: React.FunctionComponent = () => { | ||
const [ searchParams, setSearchParams ] = useSearchParams(); | ||
const { sortBy, direction, onSort } = useDataViewSort({ searchParams, setSearchParams }); | ||
const sortByIndex = useMemo(() => COLUMNS.findIndex(item => item.key === sortBy), [ sortBy ]); | ||
|
||
const getSortParams = (columnIndex: number): ThProps['sort'] => ({ | ||
sortBy: { | ||
index: sortByIndex, | ||
direction, | ||
defaultDirection: 'asc' | ||
}, | ||
onSort: (_event, index, direction) => onSort(_event, COLUMNS[index].key, direction), | ||
columnIndex | ||
}); | ||
|
||
const columns: DataViewTh[] = COLUMNS.map((column, index) => ({ | ||
cell: column.label, | ||
props: { sort: getSortParams(index) } | ||
})); | ||
|
||
const rows: DataViewTr[] = useMemo(() => sortData(repositories, sortBy, direction).map(({ name, branches, prs, workspaces, lastCommit }) => [ | ||
name, | ||
branches, | ||
prs, | ||
workspaces, | ||
lastCommit, | ||
]), [ sortBy, direction ]); | ||
|
||
return ( | ||
<DataViewTable | ||
aria-label="Repositories table" | ||
ouiaId={ouiaId} | ||
columns={columns} | ||
rows={rows} | ||
/> | ||
); | ||
}; | ||
|
||
export const BasicExample: React.FunctionComponent = () => ( | ||
<BrowserRouter> | ||
<MyTable/> | ||
</BrowserRouter> | ||
) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './pagination'; | ||
export * from './selection'; | ||
export * from './filters'; | ||
export * from './sort'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import '@testing-library/jest-dom'; | ||
import { renderHook, act } from '@testing-library/react'; | ||
import { useDataViewSort, UseDataViewSortProps, DataViewSortConfig, DataViewSortParams } from './sort'; | ||
|
||
describe('useDataViewSort', () => { | ||
const initialSort: DataViewSortConfig = { sortBy: 'name', direction: 'asc' }; | ||
|
||
it('should initialize with provided initial sort config', () => { | ||
const { result } = renderHook(() => useDataViewSort({ initialSort })); | ||
expect(result.current).toEqual(expect.objectContaining(initialSort)); | ||
}); | ||
|
||
it('should initialize with empty sort config if no initialSort is provided', () => { | ||
const { result } = renderHook(() => useDataViewSort()); | ||
expect(result.current).toEqual(expect.objectContaining({ sortBy: undefined, direction: 'asc' })); | ||
}); | ||
|
||
it('should update sort state when onSort is called', () => { | ||
const { result } = renderHook(() => useDataViewSort({ initialSort })); | ||
act(() => { | ||
result.current.onSort(undefined, 'age', 'desc'); | ||
}); | ||
expect(result.current).toEqual(expect.objectContaining({ sortBy: 'age', direction: 'desc' })); | ||
}); | ||
|
||
it('should sync with URL search params if isUrlSyncEnabled', () => { | ||
const searchParams = new URLSearchParams(); | ||
const setSearchParams = jest.fn(); | ||
const props: UseDataViewSortProps = { | ||
initialSort, | ||
searchParams, | ||
setSearchParams, | ||
}; | ||
|
||
const { result } = renderHook(() => useDataViewSort(props)); | ||
|
||
expect(setSearchParams).toHaveBeenCalledTimes(1); | ||
expect(result.current).toEqual(expect.objectContaining(initialSort)); | ||
}); | ||
|
||
it('should validate direction and fallback to default direction if invalid direction is provided', () => { | ||
const searchParams = new URLSearchParams(); | ||
searchParams.set(DataViewSortParams.SORT_BY, 'name'); | ||
searchParams.set(DataViewSortParams.DIRECTION, 'invalid-direction'); | ||
const { result } = renderHook(() => useDataViewSort({ searchParams, defaultDirection: 'desc' })); | ||
|
||
expect(result.current).toEqual(expect.objectContaining({ sortBy: 'name', direction: 'desc' })); | ||
}); | ||
|
||
it('should update search params when URL sync is enabled and sort changes', () => { | ||
const searchParams = new URLSearchParams(); | ||
const setSearchParams = jest.fn(); | ||
const props: UseDataViewSortProps = { | ||
initialSort, | ||
searchParams, | ||
setSearchParams, | ||
}; | ||
|
||
const { result } = renderHook(() => useDataViewSort(props)); | ||
act(() => { | ||
expect(setSearchParams).toHaveBeenCalledTimes(1); | ||
result.current.onSort(undefined, 'priority', 'desc'); | ||
}); | ||
|
||
expect(setSearchParams).toHaveBeenCalledTimes(2); | ||
expect(result.current).toEqual(expect.objectContaining({ sortBy: 'priority', direction: 'desc' })); | ||
}); | ||
|
||
it('should prioritize searchParams values', () => { | ||
const searchParams = new URLSearchParams(); | ||
searchParams.set(DataViewSortParams.SORT_BY, 'category'); | ||
searchParams.set(DataViewSortParams.DIRECTION, 'desc'); | ||
|
||
const { result } = renderHook( | ||
(props: UseDataViewSortProps) => useDataViewSort(props), | ||
{ initialProps: { initialSort, searchParams } } | ||
); | ||
|
||
expect(result.current).toEqual(expect.objectContaining({ | ||
sortBy: 'category', | ||
direction: 'desc', | ||
})); | ||
}); | ||
}); |
Oops, something went wrong.