Grafana/ui: display all selected levels for Cascader (#31729)

* add support for displaying all selected levels for cascader

* add story for Cascader with display all selected levels

* add tests for Cascader with displayAllSelectedLevels prop

* replace enzyme test with RTL for cascader
This commit is contained in:
Vicky Lee 2021-03-05 18:26:31 +00:00 committed by GitHub
parent d09afbda18
commit 6819a25add
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 81 additions and 31 deletions

View File

@ -59,6 +59,7 @@ export const Simple = Template.bind({});
Simple.args = {
separator: '',
};
export const WithInitialValue = Template.bind({});
WithInitialValue.args = {
initialValue: '3',
@ -70,3 +71,9 @@ WithCustomValue.args = {
allowCustomValue: true,
formatCreateLabel: (val) => 'Custom Label' + val,
};
export const WithDisplayAllSelectedLevels = Template.bind({});
WithDisplayAllSelectedLevels.args = {
displayAllSelectedLevels: true,
separator: ',',
};

View File

@ -1,6 +1,7 @@
import React from 'react';
import { Cascader } from './Cascader';
import { shallow } from 'enzyme';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
const options = [
{
@ -27,36 +28,73 @@ const options = [
},
];
const flatOptions = [
{
singleLabel: 'Second',
label: 'First / Second',
value: ['1', '2'],
},
{
singleLabel: 'Third',
label: 'First / Third',
value: ['1', '3'],
},
{
singleLabel: 'Fourth',
label: 'First / Fourth',
value: ['1', '4'],
},
{
singleLabel: 'FirstFirst',
label: 'FirstFirst',
value: ['5'],
},
];
describe('Cascader', () => {
let cascader: any;
beforeEach(() => {
cascader = shallow(<Cascader options={options} onSelect={() => {}} />);
const placeholder = 'cascader-placeholder';
it('filters results when searching', () => {
render(<Cascader placeholder={placeholder} options={options} onSelect={() => {}} />);
userEvent.type(screen.getByPlaceholderText(placeholder), 'Third');
expect(screen.queryByText('Second')).not.toBeInTheDocument();
expect(screen.getByText('First / Third')).toBeInTheDocument();
});
it('Should convert options to searchable strings', () => {
expect(cascader.state('searchableOptions')).toEqual(flatOptions);
it('displays all levels selected with default separator when displayAllSelectedLevels is true', () => {
render(
<Cascader displayAllSelectedLevels={true} placeholder={placeholder} options={options} onSelect={() => {}} />
);
expect(screen.queryByDisplayValue('First/Second')).not.toBeInTheDocument();
userEvent.click(screen.getByPlaceholderText(placeholder));
userEvent.click(screen.getByText('First'));
userEvent.click(screen.getByText('Second'));
expect(screen.getByDisplayValue('First/Second')).toBeInTheDocument();
});
it('displays all levels selected with separator passed in when displayAllSelectedLevels is true', () => {
const separator = ',';
render(
<Cascader
displayAllSelectedLevels={true}
separator={separator}
placeholder={placeholder}
options={options}
onSelect={() => {}}
/>
);
expect(screen.queryByDisplayValue('First/Second')).not.toBeInTheDocument();
userEvent.click(screen.getByPlaceholderText(placeholder));
userEvent.click(screen.getByText('First'));
userEvent.click(screen.getByText('Second'));
expect(screen.getByDisplayValue(`First${separator}Second`)).toBeInTheDocument();
});
it('displays last level selected when displayAllSelectedLevels is false', () => {
render(
<Cascader displayAllSelectedLevels={false} placeholder={placeholder} options={options} onSelect={() => {}} />
);
userEvent.click(screen.getByPlaceholderText(placeholder));
userEvent.click(screen.getByText('First'));
userEvent.click(screen.getByText('Second'));
expect(screen.getByDisplayValue('Second')).toBeInTheDocument();
});
it('displays last level selected when displayAllSelectedLevels is not passed in', () => {
render(<Cascader placeholder={placeholder} options={options} onSelect={() => {}} />);
userEvent.click(screen.getByPlaceholderText(placeholder));
userEvent.click(screen.getByText('First'));
userEvent.click(screen.getByText('Second'));
expect(screen.getByDisplayValue('Second')).toBeInTheDocument();
});
});

View File

@ -22,6 +22,7 @@ export interface CascaderProps {
allowCustomValue?: boolean;
/** A function for formatting the message for custom value creation. Only applies when allowCustomValue is set to true*/
formatCreateLabel?: (val: string) => string;
displayAllSelectedLevels?: boolean;
}
interface CascaderState {
@ -57,6 +58,8 @@ const disableDivFocus = css(`
}
`);
const DEFAULT_SEPARATOR = '/';
export class Cascader extends React.PureComponent<CascaderProps, CascaderState> {
constructor(props: CascaderProps) {
super(props);
@ -81,7 +84,7 @@ export class Cascader extends React.PureComponent<CascaderProps, CascaderState>
if (!option.items) {
selectOptions.push({
singleLabel: cpy[cpy.length - 1].label,
label: cpy.map((o) => o.label).join(this.props.separator || ' / '),
label: cpy.map((o) => o.label).join(this.props.separator || ` ${DEFAULT_SEPARATOR} `),
value: cpy.map((o) => o.value),
});
} else {
@ -116,7 +119,9 @@ export class Cascader extends React.PureComponent<CascaderProps, CascaderState>
this.setState({
rcValue: value,
focusCascade: true,
activeLabel: selectedOptions[selectedOptions.length - 1].label,
activeLabel: this.props.displayAllSelectedLevels
? selectedOptions.map((option) => option.label).join(this.props.separator || DEFAULT_SEPARATOR)
: selectedOptions[selectedOptions.length - 1].label,
});
this.props.onSelect(selectedOptions[selectedOptions.length - 1].value);