From be8f8d9c1fc946b0d69871907cc78d9e4e942e42 Mon Sep 17 00:00:00 2001 From: "Michael S. Molina" <70410625+michael-s-molina@users.noreply.github.com> Date: Fri, 26 Mar 2021 09:29:03 -0300 Subject: [PATCH] test: Adds storybook and tests to PopoverDropdown component (#13547) * test: Adds storybook and tests to PopoverDropdown component * Changes to use gridUnit and theme hook --- .../PopoverDropdown.stories.tsx | 94 ++++++++++++++++ .../PopoverDropdown/PopoverDropdown.test.tsx | 86 ++++++++++++++ .../PopoverDropdown/index.tsx} | 106 ++++++++---------- .../components/gridComponents/Header.jsx | 30 ++--- .../menu/BackgroundStyleDropdown.tsx | 4 +- .../components/menu/MarkdownModeDropdown.tsx | 4 +- 6 files changed, 249 insertions(+), 75 deletions(-) create mode 100644 superset-frontend/src/components/PopoverDropdown/PopoverDropdown.stories.tsx create mode 100644 superset-frontend/src/components/PopoverDropdown/PopoverDropdown.test.tsx rename superset-frontend/src/{dashboard/components/menu/PopoverDropdown.tsx => components/PopoverDropdown/index.tsx} (55%) diff --git a/superset-frontend/src/components/PopoverDropdown/PopoverDropdown.stories.tsx b/superset-frontend/src/components/PopoverDropdown/PopoverDropdown.stories.tsx new file mode 100644 index 000000000000..fcdb8345b714 --- /dev/null +++ b/superset-frontend/src/components/PopoverDropdown/PopoverDropdown.stories.tsx @@ -0,0 +1,94 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { useState } from 'react'; +import PopoverDropdown, { PopoverDropdownProps, OptionProps } from '.'; + +export default { + title: 'PopoverDropdown', + includeStories: ['InteractivePopoverDropdown'], +}; + +export const OPTIONS: OptionProps[] = [ + { label: 'Option A', value: 'A' }, + { label: 'Option B', value: 'B' }, + { label: 'Option C', value: 'C' }, +]; + +type ElementType = 'default' | 'button'; + +type Props = PopoverDropdownProps & { + buttonType: ElementType; + optionType: ElementType; +}; + +export const InteractivePopoverDropdown = (props: Props) => { + const { value, buttonType, optionType, ...rest } = props; + const [currentValue, setCurrentValue] = useState(value); + + const newElementHandler = (type: ElementType) => ({ + label, + value, + }: OptionProps) => { + if (type === 'button') { + return ( + + ); + } + return {label}; + }; + + return ( + setCurrentValue(selected as string)} + /> + ); +}; + +InteractivePopoverDropdown.argTypes = { + buttonType: { + defaultValue: 'default', + control: { type: 'radio', options: ['default', 'button'] }, + }, + optionType: { + defaultValue: 'default', + control: { type: 'radio', options: ['default', 'button'] }, + }, + value: { + defaultValue: OPTIONS[0].value, + table: { disable: true }, + }, + options: { + defaultValue: OPTIONS, + table: { disable: true }, + }, +}; + +InteractivePopoverDropdown.story = { + parameters: { + knobs: { + disable: true, + }, + }, +}; diff --git a/superset-frontend/src/components/PopoverDropdown/PopoverDropdown.test.tsx b/superset-frontend/src/components/PopoverDropdown/PopoverDropdown.test.tsx new file mode 100644 index 000000000000..2704e11e0cd4 --- /dev/null +++ b/superset-frontend/src/components/PopoverDropdown/PopoverDropdown.test.tsx @@ -0,0 +1,86 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { render, screen } from 'spec/helpers/testing-library'; +import userEvent from '@testing-library/user-event'; +import PopoverDropdown, { + PopoverDropdownProps, + OptionProps, +} from 'src/components/PopoverDropdown'; + +const defaultProps: PopoverDropdownProps = { + id: 'popover-dropdown', + options: [ + { label: 'Option 1', value: '1' }, + { label: 'Option 2', value: '2' }, + ], + value: '1', + renderButton: (option: OptionProps) => {option.label}, + renderOption: (option: OptionProps) =>
{option.label}
, + onChange: jest.fn(), +}; + +test('renders with default props', () => { + render(); + expect(screen.getByRole('button')).toBeInTheDocument(); + expect(screen.getByRole('button')).toHaveTextContent('Option 1'); +}); + +test('renders the menu on click', () => { + render(); + userEvent.click(screen.getByRole('button')); + expect(screen.getByRole('menu')).toBeInTheDocument(); +}); + +test('renders with custom button', () => { + render( + ( + + )} + />, + ); + expect(screen.getByText('Custom Option 1')).toBeInTheDocument(); +}); + +test('renders with custom option', () => { + render( + ( + + )} + />, + ); + userEvent.click(screen.getByRole('button')); + expect(screen.getByText('Custom Option 1')).toBeInTheDocument(); +}); + +test('triggers onChange', () => { + render(); + userEvent.click(screen.getByRole('button')); + expect(screen.getByText('Option 2')).toBeInTheDocument(); + userEvent.click(screen.getByText('Option 2')); + expect(defaultProps.onChange).toHaveBeenCalled(); +}); diff --git a/superset-frontend/src/dashboard/components/menu/PopoverDropdown.tsx b/superset-frontend/src/components/PopoverDropdown/index.tsx similarity index 55% rename from superset-frontend/src/dashboard/components/menu/PopoverDropdown.tsx rename to superset-frontend/src/components/PopoverDropdown/index.tsx index 15f56edb5b63..1bef4c65218f 100644 --- a/superset-frontend/src/dashboard/components/menu/PopoverDropdown.tsx +++ b/superset-frontend/src/components/PopoverDropdown/index.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import cx from 'classnames'; -import { styled, withTheme, SupersetThemeProps } from '@superset-ui/core'; +import { styled, useTheme } from '@superset-ui/core'; import { Dropdown, Menu } from 'src/common/components'; import Icon from 'src/components/Icon'; @@ -31,14 +31,17 @@ export interface OptionProps { export type OnChangeHandler = (key: React.Key) => void; export type RenderElementHandler = (option: OptionProps) => JSX.Element; -interface PopoverDropdownProps { +export interface PopoverDropdownProps { id: string; options: OptionProps[]; onChange: OnChangeHandler; value: string; - theme: SupersetThemeProps['theme']; - renderButton: RenderElementHandler; - renderOption: RenderElementHandler; + renderButton?: RenderElementHandler; + renderOption?: RenderElementHandler; +} + +interface HandleSelectProps { + key: React.Key; } const MenuItem = styled(Menu.Item)` @@ -71,58 +74,45 @@ const MenuItem = styled(Menu.Item)` } `; -interface HandleSelectProps { - key: React.Key; -} - -class PopoverDropdown extends React.PureComponent { - constructor(props: PopoverDropdownProps) { - super(props); - - this.handleSelect = this.handleSelect.bind(this); - } - - handleSelect({ key }: HandleSelectProps) { - this.props.onChange(key); - } - - static defaultProps = { - renderButton: (option: OptionProps) => option.label, - renderOption: (option: OptionProps) => ( +const PopoverDropdown = (props: PopoverDropdownProps) => { + const { + value, + options, + onChange, + renderButton = (option: OptionProps) => option.label, + renderOption = (option: OptionProps) => (
{option.label}
), - }; - - render() { - const { value, options, renderButton, renderOption, theme } = this.props; - const selected = options.find(opt => opt.value === value); - return ( - - {options.map(option => ( - - {renderOption(option)} - - ))} - - } - > -
- {selected && renderButton(selected)} - -
-
- ); - } -} - -export default withTheme(PopoverDropdown); + } = props; + + const theme = useTheme(); + const selected = options.find(opt => opt.value === value); + return ( + onChange(key)}> + {options.map(option => ( + + {renderOption(option)} + + ))} + + } + > +
+ {selected && renderButton(selected)} + +
+
+ ); +}; + +export default PopoverDropdown; diff --git a/superset-frontend/src/dashboard/components/gridComponents/Header.jsx b/superset-frontend/src/dashboard/components/gridComponents/Header.jsx index 3e380773f185..ef770b48e432 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Header.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Header.jsx @@ -20,19 +20,22 @@ import React from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; -import DragDroppable from '../dnd/DragDroppable'; -import DragHandle from '../dnd/DragHandle'; -import EditableTitle from '../../../components/EditableTitle'; -import AnchorLink from '../../../components/AnchorLink'; -import HoverMenu from '../menu/HoverMenu'; -import WithPopoverMenu from '../menu/WithPopoverMenu'; -import BackgroundStyleDropdown from '../menu/BackgroundStyleDropdown'; -import DeleteComponentButton from '../DeleteComponentButton'; -import PopoverDropdown from '../menu/PopoverDropdown'; -import headerStyleOptions from '../../util/headerStyleOptions'; -import backgroundStyleOptions from '../../util/backgroundStyleOptions'; -import { componentShape } from '../../util/propShapes'; -import { SMALL_HEADER, BACKGROUND_TRANSPARENT } from '../../util/constants'; +import DragDroppable from 'src/dashboard/components/dnd/DragDroppable'; +import DragHandle from 'src/dashboard/components/dnd/DragHandle'; +import EditableTitle from 'src/components/EditableTitle'; +import AnchorLink from 'src/components/AnchorLink'; +import HoverMenu from 'src/dashboard/components/menu/HoverMenu'; +import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu'; +import BackgroundStyleDropdown from 'src/dashboard/components/menu/BackgroundStyleDropdown'; +import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton'; +import PopoverDropdown from 'src/components/PopoverDropdown'; +import headerStyleOptions from 'src/dashboard/util/headerStyleOptions'; +import backgroundStyleOptions from 'src/dashboard/util/backgroundStyleOptions'; +import { componentShape } from 'src/dashboard/util/propShapes'; +import { + SMALL_HEADER, + BACKGROUND_TRANSPARENT, +} from 'src/dashboard/util/constants'; const propTypes = { id: PropTypes.string.isRequired, @@ -143,7 +146,6 @@ class Header extends React.PureComponent { options={headerStyleOptions} value={component.meta.headerSize} onChange={this.handleChangeSize} - renderTitle={option => `${option.label} header`} />,