Skip to content

Commit

Permalink
test: Adds storybook and tests to PopoverDropdown component (#13547)
Browse files Browse the repository at this point in the history
* test: Adds storybook and tests to PopoverDropdown component

* Changes to use gridUnit and theme hook
  • Loading branch information
michael-s-molina committed Mar 26, 2021
1 parent e61f5a9 commit be8f8d9
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 75 deletions.
Original file line number Diff line number Diff line change
@@ -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,
},
},
};
Original file line number Diff line number Diff line change
@@ -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();
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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)`
Expand Down Expand Up @@ -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>
),
};

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>
);
}
}

export default withTheme(PopoverDropdown);
} = props;

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 PopoverDropdown;
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit be8f8d9

Please sign in to comment.