Skip to content

Commit

Permalink
Merge pull request #146 from fhlavac/update
Browse files Browse the repository at this point in the history
Pull new features from v5
  • Loading branch information
fhlavac authored Nov 22, 2024
2 parents 50205fd + ed4300d commit c5aea97
Show file tree
Hide file tree
Showing 43 changed files with 3,574 additions and 560 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface MyComponentProps {
const useStyles = createUseStyles({
myText: {
fontFamily: 'monospace',
fontSize: 'var(--pf-v5-global--icon--FontSize--md)',
fontSize: 'var(--pf-v6-global--icon--FontSize--md)',
},
})
Expand Down Expand Up @@ -126,7 +126,7 @@ When adding/making changes to a component, always make sure your code is tested:
### Styling:
- for styling always use JSS
- 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.)
- 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)
- 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)

---

Expand Down
66 changes: 66 additions & 0 deletions cypress/component/DataViewCheckboxFilter.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import { DataViewCheckboxFilter, DataViewCheckboxFilterProps } from '@patternfly/react-data-view/dist/dynamic/DataViewCheckboxFilter';
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';

describe('DataViewCheckboxFilter component', () => {
const defaultProps: DataViewCheckboxFilterProps = {
filterId: 'test-checkbox-filter',
title: 'Test checkbox filter',
value: [ 'workspace-one' ],
options: [
{ label: 'Workspace one', value: 'workspace-one' },
{ label: 'Workspace two', value: 'workspace-two' },
{ label: 'Workspace three', value: 'workspace-three' },
],
};

it('renders a checkbox filter with options', () => {
const onChange = cy.stub().as('onChange');

cy.mount(
<DataViewToolbar filters={<DataViewCheckboxFilter {...defaultProps} onChange={onChange} />} />
);

cy.get('[data-ouia-component-id="DataViewCheckboxFilter-toggle"]')
.contains('Test checkbox filter')
.should('be.visible');

cy.get('[data-ouia-component-id="DataViewCheckboxFilter-badge"]')
.should('exist')
.contains('1');

cy.get('[data-ouia-component-id="DataViewCheckboxFilter-toggle"]').click();
cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]').should('be.visible');

cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]')
.find('li')
.should('have.length', 3)
.first()
.contains('Workspace one');

cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]')
.find('li')
.first()
.find('input[type="checkbox"]')
.should('be.checked');

cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]')
.find('li')
.eq(1)
.find('input[type="checkbox"]')
.click();

cy.get('@onChange').should('have.been.calledWith', Cypress.sinon.match.object, [ 'workspace-two', 'workspace-one' ]);
});

it('renders a checkbox filter with no options selected', () => {
const emptyProps = { ...defaultProps, value: [] };

cy.mount(
<DataViewToolbar filters={<DataViewCheckboxFilter {...emptyProps} />} />
);

cy.get('[data-ouia-component-id="DataViewCheckboxFilter-toggle"]').contains('Test checkbox filter');
cy.get('[data-ouia-component-id="DataViewCheckboxFilter-badge"]').should('not.exist');
});
});
113 changes: 113 additions & 0 deletions cypress/component/DataViewFilters.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from 'react';
import { useDataViewFilters } from '@patternfly/react-data-view/dist/dynamic/Hooks';
import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
import { DataViewToolbar } from '@patternfly/react-data-view/dist/esm/DataViewToolbar';
import { FilterIcon } from '@patternfly/react-icons';

const filtersProps = {
ouiaId: 'DataViewFilters',
toggleIcon: <FilterIcon />,
values: { name: '', branch: '' }
};

interface RepositoryFilters {
name: string,
branch: string
};

const DataViewToolbarWithState = (props: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
const { filters, onSetFilters, clearAllFilters } = useDataViewFilters<RepositoryFilters>({ initialFilters: { name: '', branch: '' } });

return (
<DataViewToolbar
ouiaId='FiltersExampleHeader'
clearAllFilters = {clearAllFilters}
filters={
<DataViewFilters {...filtersProps} onChange={(_e, values) => onSetFilters(values)} values={filters} {...props}>
<DataViewTextFilter filterId="name" title='Name' placeholder='Filter by name' />
<DataViewTextFilter filterId="branch" title='Branch' placeholder='Filter by branch' />
</DataViewFilters>
}
/>
);
};

describe('DataViewFilters', () => {
it('renders DataViewFilters with menu and filter items', () => {
cy.mount(<DataViewToolbarWithState />);
cy.get('[data-ouia-component-id="DataViewFilters"]').should('exist');
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();

cy.contains('Name').should('exist');
cy.contains('Branch').should('exist');
});

it('can select a filter option', () => {
cy.mount(<DataViewToolbarWithState />);
cy.get('[data-ouia-component-id="DataViewFilters"]').should('contain.text', 'Name');
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
cy.contains('Branch').click();

cy.get('[data-ouia-component-id="DataViewFilters"]').should('contain.text', 'Branch');
});

it('responds to input and clears the filters', () => {
cy.mount(<DataViewToolbarWithState />);
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
cy.contains('Name').click();

cy.get('input[placeholder="Filter by name"]').type('Repository one');
cy.get('.pf-v6-c-label__text').should('have.length', 1);
cy.get('input[placeholder="Filter by name"]').clear();
cy.get('.pf-v6-c-label__text').should('have.length', 0);
});

it('displays labels for selected filters', () => {
cy.mount(<DataViewToolbarWithState />);
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
cy.contains('Name').click();
cy.get('input[placeholder="Filter by name"]').type('Repository one');

cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
cy.contains('Branch').click();
cy.get('input[placeholder="Filter by branch"]').type('Main branch');

cy.get('.pf-v6-c-label__text').should('have.length', 2);
cy.get('.pf-v6-c-label__text').eq(0).should('contain.text', 'Repository one');
cy.get('.pf-v6-c-label__text').eq(1).should('contain.text', 'Main branch');
});

it('removes filters by clicking individual labels', () => {
cy.mount(<DataViewToolbarWithState />);
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
cy.contains('Name').click();
cy.get('input[placeholder="Filter by name"]').type('Repository one');

cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
cy.contains('Branch').click();
cy.get('input[placeholder="Filter by branch"]').type('Main branch');

cy.get('[aria-label="Close Repository one"]').should('have.length', 1);
cy.get('[aria-label="Close Main branch"]').should('have.length', 1);

cy.get('[aria-label="Close Repository one"]').click();
cy.get('[aria-label="Close Repository one"]').should('have.length', 0);

cy.get('[aria-label="Close Main branch"]').click();
cy.get('[aria-label="Close Main branch"]').should('have.length', 0);
});

it('clears all filters using the clear-all button', () => {
cy.mount(<DataViewToolbarWithState />);
cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
cy.contains('Name').click();
cy.get('input[placeholder="Filter by name"]').type('Repository one');

cy.get('[data-ouia-component-id="DataViewFilters"] .pf-v6-c-menu-toggle').click();
cy.contains('Branch').click();
cy.get('input[placeholder="Filter by branch"]').type('Main branch');

cy.get('[data-ouia-component-id="FiltersExampleHeader-clear-all-filters"]').should('exist').click();
});
});
117 changes: 117 additions & 0 deletions cypress/component/DataViewTableSorting.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/* 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"]')
.find('button')
.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"]')
.find('button')
.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"]')
.find('button')
.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"]')
.find('button')
.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');
});
});
75 changes: 75 additions & 0 deletions cypress/component/DataViewTextFilter.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useState } from 'react';
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';

const defaultProps = {
filterId: 'name',
title: 'Name',
value: '',
ouiaId: 'DataViewTextFilter',
placeholder: 'Filter by name'
};

const DataViewToolbarWithState = (props: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
const [ value, setValue ] = useState('Repository one');

return (
<DataViewToolbar clearAllFilters={() => setValue('')}>
<DataViewTextFilter {...defaultProps} value={value} onChange={() => setValue('')} {...props} />
</DataViewToolbar>
);
};

describe('DataViewTextFilter', () => {

it('renders DataViewTextFilter with correct initial values', () => {
cy.mount(<DataViewToolbarWithState value="" />);
cy.get('[data-ouia-component-id="DataViewTextFilter"]').should('exist');
cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input')
.should('have.attr', 'placeholder', 'Filter by name')
.and('have.value', '');
});

it('accepts input when passed', () => {
cy.mount(<DataViewToolbarWithState value="" />);
cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input')
.type('Repository one')
.should('have.value', 'Repository one');
});

it('displays a label when value is present and removes it on delete', () => {
cy.mount(<DataViewToolbarWithState />);
cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', 'Repository one');

cy.get('.pf-v6-c-label__text').contains('Repository one');
cy.get('.pf-m-label-group button.pf-v6-c-button.pf-m-plain').click();

cy.get('.pf-v6-c-label__text').should('not.exist');
cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', '');
});

it('clears input when the clear button is clicked', () => {
cy.mount(<DataViewToolbarWithState />);
cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', 'Repository one');

cy.get('[data-ouia-component-id="DataViewToolbar-clear-all-filters"]').click();

cy.get('[data-ouia-component-id="DataViewTextFilter-input"] input').should('have.value', '');
});

it('hides or shows the toolbar item based on showToolbarItem prop', () => {
cy.mount(
<DataViewToolbar>
<DataViewTextFilter {...defaultProps} showToolbarItem={false} />
</DataViewToolbar>
);
cy.get('[data-ouia-component-id="DataViewTextFilter"]').should('not.exist');

cy.mount(
<DataViewToolbar>
<DataViewTextFilter {...defaultProps} showToolbarItem />
</DataViewToolbar>
);
cy.get('[data-ouia-component-id="DataViewTextFilter"]').should('exist');
});
});
Loading

0 comments on commit c5aea97

Please sign in to comment.