Skip to content

Commit c5aea97

Browse files
authored
Merge pull request #146 from fhlavac/update
Pull new features from v5
2 parents 50205fd + ed4300d commit c5aea97

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+3574
-560
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export interface MyComponentProps {
2626
const useStyles = createUseStyles({
2727
myText: {
2828
fontFamily: 'monospace',
29-
fontSize: 'var(--pf-v5-global--icon--FontSize--md)',
29+
fontSize: 'var(--pf-v6-global--icon--FontSize--md)',
3030
},
3131
})
3232
@@ -126,7 +126,7 @@ When adding/making changes to a component, always make sure your code is tested:
126126
### Styling:
127127
- for styling always use JSS
128128
- new classNames should be named in camelCase starting with the name of a given component and following with more details clarifying its purpose/component's subsection to which the class is applied (`actionMenu`, `actionMenuDropdown`, `actionMenuDropdownToggle`, etc.)
129-
- do not use `pf-v5-u-XXX` classes, use CSS variables in a custom class instead (styles for the utility classes are not bundled with the standard patternfly.css - it would require the consumer to import also addons.css)
129+
- do not use `pf-v6-u-XXX` classes, use CSS variables in a custom class instead (styles for the utility classes are not bundled with the standard patternfly.css - it would require the consumer to import also addons.css)
130130

131131
---
132132

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React from 'react';
2+
import { DataViewCheckboxFilter, DataViewCheckboxFilterProps } from '@patternfly/react-data-view/dist/dynamic/DataViewCheckboxFilter';
3+
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
4+
5+
describe('DataViewCheckboxFilter component', () => {
6+
const defaultProps: DataViewCheckboxFilterProps = {
7+
filterId: 'test-checkbox-filter',
8+
title: 'Test checkbox filter',
9+
value: [ 'workspace-one' ],
10+
options: [
11+
{ label: 'Workspace one', value: 'workspace-one' },
12+
{ label: 'Workspace two', value: 'workspace-two' },
13+
{ label: 'Workspace three', value: 'workspace-three' },
14+
],
15+
};
16+
17+
it('renders a checkbox filter with options', () => {
18+
const onChange = cy.stub().as('onChange');
19+
20+
cy.mount(
21+
<DataViewToolbar filters={<DataViewCheckboxFilter {...defaultProps} onChange={onChange} />} />
22+
);
23+
24+
cy.get('[data-ouia-component-id="DataViewCheckboxFilter-toggle"]')
25+
.contains('Test checkbox filter')
26+
.should('be.visible');
27+
28+
cy.get('[data-ouia-component-id="DataViewCheckboxFilter-badge"]')
29+
.should('exist')
30+
.contains('1');
31+
32+
cy.get('[data-ouia-component-id="DataViewCheckboxFilter-toggle"]').click();
33+
cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]').should('be.visible');
34+
35+
cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]')
36+
.find('li')
37+
.should('have.length', 3)
38+
.first()
39+
.contains('Workspace one');
40+
41+
cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]')
42+
.find('li')
43+
.first()
44+
.find('input[type="checkbox"]')
45+
.should('be.checked');
46+
47+
cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]')
48+
.find('li')
49+
.eq(1)
50+
.find('input[type="checkbox"]')
51+
.click();
52+
53+
cy.get('@onChange').should('have.been.calledWith', Cypress.sinon.match.object, [ 'workspace-two', 'workspace-one' ]);
54+
});
55+
56+
it('renders a checkbox filter with no options selected', () => {
57+
const emptyProps = { ...defaultProps, value: [] };
58+
59+
cy.mount(
60+
<DataViewToolbar filters={<DataViewCheckboxFilter {...emptyProps} />} />
61+
);
62+
63+
cy.get('[data-ouia-component-id="DataViewCheckboxFilter-toggle"]').contains('Test checkbox filter');
64+
cy.get('[data-ouia-component-id="DataViewCheckboxFilter-badge"]').should('not.exist');
65+
});
66+
});
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import React from 'react';
2+
import { useDataViewFilters } from '@patternfly/react-data-view/dist/dynamic/Hooks';
3+
import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
4+
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
5+
import { DataViewToolbar } from '@patternfly/react-data-view/dist/esm/DataViewToolbar';
6+
import { FilterIcon } from '@patternfly/react-icons';
7+
8+
const filtersProps = {
9+
ouiaId: 'DataViewFilters',
10+
toggleIcon: <FilterIcon />,
11+
values: { name: '', branch: '' }
12+
};
13+
14+
interface RepositoryFilters {
15+
name: string,
16+
branch: string
17+
};
18+
19+
const DataViewToolbarWithState = (props: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
20+
const { filters, onSetFilters, clearAllFilters } = useDataViewFilters<RepositoryFilters>({ initialFilters: { name: '', branch: '' } });
21+
22+
return (
23+
<DataViewToolbar
24+
ouiaId='FiltersExampleHeader'
25+
clearAllFilters = {clearAllFilters}
26+
filters={
27+
<DataViewFilters {...filtersProps} onChange={(_e, values) => onSetFilters(values)} values={filters} {...props}>
28+
<DataViewTextFilter filterId="name" title='Name' placeholder='Filter by name' />
29+
<DataViewTextFilter filterId="branch" title='Branch' placeholder='Filter by branch' />
30+
</DataViewFilters>
31+
}
32+
/>
33+
);
34+
};
35+
36+
describe('DataViewFilters', () => {
37+
it('renders DataViewFilters with menu and filter items', () => {
38+
cy.mount(<DataViewToolbarWithState />);
39+
cy.get('[data-ouia-component-id="DataViewFilters"]').should('exist');
40+
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
41+
42+
cy.contains('Name').should('exist');
43+
cy.contains('Branch').should('exist');
44+
});
45+
46+
it('can select a filter option', () => {
47+
cy.mount(<DataViewToolbarWithState />);
48+
cy.get('[data-ouia-component-id="DataViewFilters"]').should('contain.text', 'Name');
49+
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
50+
cy.contains('Branch').click();
51+
52+
cy.get('[data-ouia-component-id="DataViewFilters"]').should('contain.text', 'Branch');
53+
});
54+
55+
it('responds to input and clears the filters', () => {
56+
cy.mount(<DataViewToolbarWithState />);
57+
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
58+
cy.contains('Name').click();
59+
60+
cy.get('input[placeholder="Filter by name"]').type('Repository one');
61+
cy.get('.pf-v6-c-label__text').should('have.length', 1);
62+
cy.get('input[placeholder="Filter by name"]').clear();
63+
cy.get('.pf-v6-c-label__text').should('have.length', 0);
64+
});
65+
66+
it('displays labels for selected filters', () => {
67+
cy.mount(<DataViewToolbarWithState />);
68+
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
69+
cy.contains('Name').click();
70+
cy.get('input[placeholder="Filter by name"]').type('Repository one');
71+
72+
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
73+
cy.contains('Branch').click();
74+
cy.get('input[placeholder="Filter by branch"]').type('Main branch');
75+
76+
cy.get('.pf-v6-c-label__text').should('have.length', 2);
77+
cy.get('.pf-v6-c-label__text').eq(0).should('contain.text', 'Repository one');
78+
cy.get('.pf-v6-c-label__text').eq(1).should('contain.text', 'Main branch');
79+
});
80+
81+
it('removes filters by clicking individual labels', () => {
82+
cy.mount(<DataViewToolbarWithState />);
83+
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
84+
cy.contains('Name').click();
85+
cy.get('input[placeholder="Filter by name"]').type('Repository one');
86+
87+
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
88+
cy.contains('Branch').click();
89+
cy.get('input[placeholder="Filter by branch"]').type('Main branch');
90+
91+
cy.get('[aria-label="Close Repository one"]').should('have.length', 1);
92+
cy.get('[aria-label="Close Main branch"]').should('have.length', 1);
93+
94+
cy.get('[aria-label="Close Repository one"]').click();
95+
cy.get('[aria-label="Close Repository one"]').should('have.length', 0);
96+
97+
cy.get('[aria-label="Close Main branch"]').click();
98+
cy.get('[aria-label="Close Main branch"]').should('have.length', 0);
99+
});
100+
101+
it('clears all filters using the clear-all button', () => {
102+
cy.mount(<DataViewToolbarWithState />);
103+
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
104+
cy.contains('Name').click();
105+
cy.get('input[placeholder="Filter by name"]').type('Repository one');
106+
107+
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
108+
cy.contains('Branch').click();
109+
cy.get('input[placeholder="Filter by branch"]').type('Main branch');
110+
111+
cy.get('[data-ouia-component-id="FiltersExampleHeader-clear-all-filters"]').should('exist').click();
112+
});
113+
});
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/* eslint-disable no-nested-ternary */
2+
import React from 'react';
3+
import { useDataViewSort } from '@patternfly/react-data-view/dist/dynamic/Hooks';
4+
import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
5+
import { BrowserRouter, useSearchParams } from 'react-router-dom';
6+
import { ThProps } from '@patternfly/react-table';
7+
8+
interface Repository {
9+
name: string;
10+
branches: string;
11+
prs: string;
12+
workspaces: string;
13+
lastCommit: string;
14+
}
15+
16+
const COLUMNS = [
17+
{ label: 'Repository', key: 'name', index: 0 },
18+
{ label: 'Branch', key: 'branches', index: 1 },
19+
{ label: 'Pull request', key: 'prs', index: 2 },
20+
{ label: 'Workspace', key: 'workspaces', index: 3 },
21+
{ label: 'Last commit', key: 'lastCommit', index: 4 },
22+
];
23+
24+
const repositories: Repository[] = [
25+
{ name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: '2023-11-01' },
26+
{ name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: '2023-11-06' },
27+
{ name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: '2023-11-02' },
28+
{ name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: '2023-11-05' },
29+
{ name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: '2023-11-03' },
30+
{ name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: '2023-11-04' },
31+
];
32+
33+
const sortData = (data: Repository[], sortBy: keyof Repository | undefined, direction: 'asc' | 'desc' | undefined) =>
34+
sortBy && direction
35+
? [ ...data ].sort((a, b) =>
36+
direction === 'asc'
37+
? a[sortBy] < b[sortBy] ? -1 : a[sortBy] > b[sortBy] ? 1 : 0
38+
: a[sortBy] > b[sortBy] ? -1 : a[sortBy] < b[sortBy] ? 1 : 0
39+
)
40+
: data;
41+
42+
const TestTable: React.FunctionComponent = () => {
43+
const [ searchParams, setSearchParams ] = useSearchParams();
44+
const { sortBy, direction, onSort } = useDataViewSort({ searchParams, setSearchParams });
45+
const sortByIndex = React.useMemo(() => COLUMNS.findIndex(item => item.key === sortBy), [ sortBy ]);
46+
47+
const getSortParams = (columnIndex: number): ThProps['sort'] => ({
48+
sortBy: {
49+
index: sortByIndex,
50+
direction,
51+
defaultDirection: 'asc',
52+
},
53+
onSort: (_event, index, direction) => onSort(_event, COLUMNS[index].key, direction),
54+
columnIndex,
55+
});
56+
57+
const columns: DataViewTh[] = COLUMNS.map((column, index) => ({
58+
cell: column.label,
59+
props: { sort: getSortParams(index) },
60+
}));
61+
62+
const rows: DataViewTr[] = React.useMemo(
63+
() =>
64+
sortData(repositories, sortBy ? sortBy as keyof Repository : undefined, direction).map(({ name, branches, prs, workspaces, lastCommit }) => [
65+
name,
66+
branches,
67+
prs,
68+
workspaces,
69+
lastCommit,
70+
]),
71+
[ sortBy, direction ]
72+
);
73+
74+
return <DataViewTable aria-label="Repositories table" ouiaId="test-table" columns={columns} rows={rows} />;
75+
};
76+
77+
describe('DataViewTable Sorting with Hook', () => {
78+
it('sorts by repository name in ascending and descending order', () => {
79+
cy.mount(
80+
<BrowserRouter>
81+
<TestTable />
82+
</BrowserRouter>
83+
);
84+
85+
cy.get('[data-ouia-component-id="test-table-th-0"]')
86+
.find('button')
87+
.click();
88+
cy.get('[data-ouia-component-id="test-table-td-0-0"]').should('contain', 'Repository five');
89+
cy.get('[data-ouia-component-id="test-table-td-5-0"]').should('contain', 'Repository two');
90+
91+
cy.get('[data-ouia-component-id="test-table-th-0"]')
92+
.find('button')
93+
.click();
94+
cy.get('[data-ouia-component-id="test-table-td-0-0"]').should('contain', 'Repository two');
95+
cy.get('[data-ouia-component-id="test-table-td-5-0"]').should('contain', 'Repository five');
96+
});
97+
98+
it('sorts by last commit date in ascending and descending order', () => {
99+
cy.mount(
100+
<BrowserRouter>
101+
<TestTable />
102+
</BrowserRouter>
103+
);
104+
105+
cy.get('[data-ouia-component-id="test-table-th-4"]')
106+
.find('button')
107+
.click();
108+
cy.get('[data-ouia-component-id="test-table-td-0-4"]').should('contain', '2023-11-01');
109+
cy.get('[data-ouia-component-id="test-table-td-5-4"]').should('contain', '2023-11-06');
110+
111+
cy.get('[data-ouia-component-id="test-table-th-4"]')
112+
.find('button')
113+
.click();
114+
cy.get('[data-ouia-component-id="test-table-td-0-4"]').should('contain', '2023-11-06');
115+
cy.get('[data-ouia-component-id="test-table-td-5-4"]').should('contain', '2023-11-01');
116+
});
117+
});
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React, { useState } from 'react';
2+
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
3+
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
4+
5+
const defaultProps = {
6+
filterId: 'name',
7+
title: 'Name',
8+
value: '',
9+
ouiaId: 'DataViewTextFilter',
10+
placeholder: 'Filter by name'
11+
};
12+
13+
const DataViewToolbarWithState = (props: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
14+
const [ value, setValue ] = useState('Repository one');
15+
16+
return (
17+
<DataViewToolbar clearAllFilters={() => setValue('')}>
18+
<DataViewTextFilter {...defaultProps} value={value} onChange={() => setValue('')} {...props} />
19+
</DataViewToolbar>
20+
);
21+
};
22+
23+
describe('DataViewTextFilter', () => {
24+
25+
it('renders DataViewTextFilter with correct initial values', () => {
26+
cy.mount(<DataViewToolbarWithState value="" />);
27+
cy.get('[data-ouia-component-id="DataViewTextFilter"]').should('exist');
28+
cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input')
29+
.should('have.attr', 'placeholder', 'Filter by name')
30+
.and('have.value', '');
31+
});
32+
33+
it('accepts input when passed', () => {
34+
cy.mount(<DataViewToolbarWithState value="" />);
35+
cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input')
36+
.type('Repository one')
37+
.should('have.value', 'Repository one');
38+
});
39+
40+
it('displays a label when value is present and removes it on delete', () => {
41+
cy.mount(<DataViewToolbarWithState />);
42+
cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', 'Repository one');
43+
44+
cy.get('.pf-v6-c-label__text').contains('Repository one');
45+
cy.get('.pf-m-label-group button.pf-v6-c-button.pf-m-plain').click();
46+
47+
cy.get('.pf-v6-c-label__text').should('not.exist');
48+
cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', '');
49+
});
50+
51+
it('clears input when the clear button is clicked', () => {
52+
cy.mount(<DataViewToolbarWithState />);
53+
cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', 'Repository one');
54+
55+
cy.get('[data-ouia-component-id="DataViewToolbar-clear-all-filters"]').click();
56+
57+
cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', '');
58+
});
59+
60+
it('hides or shows the toolbar item based on showToolbarItem prop', () => {
61+
cy.mount(
62+
<DataViewToolbar>
63+
<DataViewTextFilter {...defaultProps} showToolbarItem={false} />
64+
</DataViewToolbar>
65+
);
66+
cy.get('[data-ouia-component-id="DataViewTextFilter"]').should('not.exist');
67+
68+
cy.mount(
69+
<DataViewToolbar>
70+
<DataViewTextFilter {...defaultProps} showToolbarItem />
71+
</DataViewToolbar>
72+
);
73+
cy.get('[data-ouia-component-id="DataViewTextFilter"]').should('exist');
74+
});
75+
});

0 commit comments

Comments
 (0)