mirror of
https://github.com/apache/superset.git
synced 2026-04-22 01:24:43 +00:00
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:
committed by
GitHub
parent
e61f5a924b
commit
be8f8d9c1f
@@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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`}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user