From 994fa868360ad379211b21caf4ce5fe2d0d324ef Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 10 Mar 2021 14:05:19 +0000 Subject: [PATCH] Grafana/ui: display all selected levels for Cascader (#31729) (#31862) * 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 (cherry picked from commit 6819a25add57def8ad1da345513280cd739bdc0e) Co-authored-by: Vicky Lee <36230812+vickyyyyyyy@users.noreply.github.com> --- .../components/Cascader/Cascader.story.tsx | 7 ++ .../src/components/Cascader/Cascader.test.tsx | 96 +++++++++++++------ .../src/components/Cascader/Cascader.tsx | 9 +- 3 files changed, 81 insertions(+), 31 deletions(-) diff --git a/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx b/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx index bdee52ad252..25a27ed5b9b 100644 --- a/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx +++ b/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx @@ -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: ',', +}; diff --git a/packages/grafana-ui/src/components/Cascader/Cascader.test.tsx b/packages/grafana-ui/src/components/Cascader/Cascader.test.tsx index ba5db94db8f..c956a131b63 100644 --- a/packages/grafana-ui/src/components/Cascader/Cascader.test.tsx +++ b/packages/grafana-ui/src/components/Cascader/Cascader.test.tsx @@ -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( {}} />); + const placeholder = 'cascader-placeholder'; + + it('filters results when searching', () => { + render( {}} />); + + 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( + {}} /> + ); + + 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( + {}} + /> + ); + + 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( + {}} /> + ); + + 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( {}} />); + + userEvent.click(screen.getByPlaceholderText(placeholder)); + userEvent.click(screen.getByText('First')); + userEvent.click(screen.getByText('Second')); + + expect(screen.getByDisplayValue('Second')).toBeInTheDocument(); }); }); diff --git a/packages/grafana-ui/src/components/Cascader/Cascader.tsx b/packages/grafana-ui/src/components/Cascader/Cascader.tsx index 99d32b49329..7f4eeff7345 100644 --- a/packages/grafana-ui/src/components/Cascader/Cascader.tsx +++ b/packages/grafana-ui/src/components/Cascader/Cascader.tsx @@ -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 { constructor(props: CascaderProps) { super(props); @@ -81,7 +84,7 @@ export class Cascader extends React.PureComponent 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 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);