test: Adds storybook and tests to PopoverDropdown component (#13547)

* test: Adds storybook and tests to PopoverDropdown component

* Changes to use gridUnit and theme hook
This commit is contained in:
Michael S. Molina
2021-03-26 09:29:03 -03:00
committed by GitHub
parent e61f5a924b
commit be8f8d9c1f
6 changed files with 247 additions and 73 deletions

View File

@@ -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 (
<button type="button" key={value}>
{label}
</button>
);
}
return <span>{label}</span>;
};
return (
<PopoverDropdown
{...rest}
value={currentValue}
renderButton={newElementHandler(buttonType)}
renderOption={newElementHandler(optionType)}
onChange={selected => 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,
},
},
};

View File

@@ -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) => <span>{option.label}</span>,
renderOption: (option: OptionProps) => <div>{option.label}</div>,
onChange: jest.fn(),
};
test('renders with default props', () => {
render(<PopoverDropdown {...defaultProps} />);
expect(screen.getByRole('button')).toBeInTheDocument();
expect(screen.getByRole('button')).toHaveTextContent('Option 1');
});
test('renders the menu on click', () => {
render(<PopoverDropdown {...defaultProps} />);
userEvent.click(screen.getByRole('button'));
expect(screen.getByRole('menu')).toBeInTheDocument();
});
test('renders with custom button', () => {
render(
<PopoverDropdown
{...defaultProps}
renderButton={({ label, value }: OptionProps) => (
<button type="button" key={value}>
{`Custom ${label}`}
</button>
)}
/>,
);
expect(screen.getByText('Custom Option 1')).toBeInTheDocument();
});
test('renders with custom option', () => {
render(
<PopoverDropdown
{...defaultProps}
renderOption={({ label, value }: OptionProps) => (
<button type="button" key={value}>
{`Custom ${label}`}
</button>
)}
/>,
);
userEvent.click(screen.getByRole('button'));
expect(screen.getByText('Custom Option 1')).toBeInTheDocument();
});
test('triggers onChange', () => {
render(<PopoverDropdown {...defaultProps} />);
userEvent.click(screen.getByRole('button'));
expect(screen.getByText('Option 2')).toBeInTheDocument();
userEvent.click(screen.getByText('Option 2'));
expect(defaultProps.onChange).toHaveBeenCalled();
});

View File

@@ -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<PopoverDropdownProps> {
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) => (
<div className={option.className}>{option.label}</div>
),
};
} = props;
render() {
const { value, options, renderButton, renderOption, theme } = this.props;
const selected = options.find(opt => opt.value === value);
return (
<Dropdown
trigger={['click']}
overlayStyle={{ zIndex: theme.zIndex.max }}
overlay={
<Menu onClick={this.handleSelect}>
{options.map(option => (
<MenuItem
id="menu-item"
key={option.value}
className={cx('dropdown-item', {
active: option.value === value,
})}
>
{renderOption(option)}
</MenuItem>
))}
</Menu>
}
>
<div role="button" css={{ display: 'flex', alignItems: 'center' }}>
{selected && renderButton(selected)}
<Icon name="caret-down" css={{ marginTop: 4 }} />
</div>
</Dropdown>
);
}
}
const theme = useTheme();
const selected = options.find(opt => opt.value === value);
return (
<Dropdown
trigger={['click']}
overlayStyle={{ zIndex: theme.zIndex.max }}
overlay={
<Menu onClick={({ key }: HandleSelectProps) => onChange(key)}>
{options.map(option => (
<MenuItem
id="menu-item"
key={option.value}
className={cx('dropdown-item', {
active: option.value === value,
})}
>
{renderOption(option)}
</MenuItem>
))}
</Menu>
}
>
<div role="button" css={{ display: 'flex', alignItems: 'center' }}>
{selected && renderButton(selected)}
<Icon name="caret-down" css={{ marginTop: theme.gridUnit }} />
</div>
</Dropdown>
);
};
export default withTheme(PopoverDropdown);
export default PopoverDropdown;

View File

@@ -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`}
/>,
<BackgroundStyleDropdown
id={`${component.id}-background`}

View File

@@ -19,11 +19,11 @@
import React from 'react';
import cx from 'classnames';
import backgroundStyleOptions from '../../util/backgroundStyleOptions';
import backgroundStyleOptions from 'src/dashboard/util/backgroundStyleOptions';
import PopoverDropdown, {
OptionProps,
OnChangeHandler,
} from './PopoverDropdown';
} from 'src/components/PopoverDropdown';
interface BackgroundStyleDropdownProps {
id: string;

View File

@@ -19,7 +19,9 @@
import React from 'react';
import { t } from '@superset-ui/core';
import PopoverDropdown, { OnChangeHandler } from './PopoverDropdown';
import PopoverDropdown, {
OnChangeHandler,
} from 'src/components/PopoverDropdown';
interface MarkdownModeDropdownProps {
id: string;