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

@@ -0,0 +1,118 @@
/**
* 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 cx from 'classnames';
import { styled, useTheme } from '@superset-ui/core';
import { Dropdown, Menu } from 'src/common/components';
import Icon from 'src/components/Icon';
export interface OptionProps {
value: string;
label: string;
className?: string;
}
export type OnChangeHandler = (key: React.Key) => void;
export type RenderElementHandler = (option: OptionProps) => JSX.Element;
export interface PopoverDropdownProps {
id: string;
options: OptionProps[];
onChange: OnChangeHandler;
value: string;
renderButton?: RenderElementHandler;
renderOption?: RenderElementHandler;
}
interface HandleSelectProps {
key: React.Key;
}
const MenuItem = styled(Menu.Item)`
&.ant-menu-item {
height: auto;
line-height: 1.4;
padding-top: ${({ theme }) => theme.gridUnit}px;
padding-bottom: ${({ theme }) => theme.gridUnit}px;
margin-top: 0;
margin-bottom: 0;
&:not(:last-child) {
margin-bottom: 0;
}
&:hover {
background: ${({ theme }) => theme.colors.grayscale.light3};
}
&.active {
font-weight: ${({ theme }) => theme.typography.weights.bold};
background: ${({ theme }) => theme.colors.grayscale.light2};
}
}
&.ant-menu-item-selected {
color: unset;
}
`;
const PopoverDropdown = (props: PopoverDropdownProps) => {
const {
value,
options,
onChange,
renderButton = (option: OptionProps) => option.label,
renderOption = (option: OptionProps) => (
<div className={option.className}>{option.label}</div>
),
} = 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;