wix-style-react
Version:
1,143 lines (1,019 loc) • 38.9 kB
JavaScript
import React from 'react';
import { consoleErrors } from 'wix-ui-test-utils/dist/src/jest-setup';
import DropdownLayout, { DIVIDER_OPTION_VALUE } from '../DropdownLayout';
import dropdownLayoutDriverFactory from '../DropdownLayout.driver';
import { dropdownLayoutDriverFactory as dropdownLayoutUniDriverFactory } from '../DropdownLayout.uni.driver';
import {
createRendererWithDriver,
createRendererWithUniDriver,
cleanup,
render as rawRender,
} from '../../../test/utils/react';
describe('DropdownLayout', () => {
describe('[sync]', () => {
runTests(createRendererWithDriver(dropdownLayoutDriverFactory));
});
describe('[async]', () => {
runTests(createRendererWithUniDriver(dropdownLayoutUniDriverFactory), true);
});
const options = [
{ id: 0, value: 'Option 1' },
{ id: 1, value: 'Option 2' },
{ id: 2, value: 'Option 3', disabled: true },
{ id: 3, value: 'Option 4' },
{ id: 'divider1', value: '-' },
{ id: 'element1', value: <span style={{ color: 'brown' }}>Option 4</span> },
{ value: '-' },
];
function runTests(render, isAsync) {
afterEach(cleanup);
const createDriver = jsx => render(jsx).driver;
it('should have be invisible and drop down by default', async () => {
const driver = createDriver(<DropdownLayout options={options} />);
expect(await driver.isShown()).toBe(false);
expect(await driver.isDown()).toBe(true);
});
it('should find an option by text', async () => {
const driver = createDriver(<DropdownLayout options={options} />);
expect(await driver.isOptionExists('Option 1')).toBe(true);
});
it('should not find an option by text', async () => {
const driver = createDriver(<DropdownLayout options={options} />);
expect(await driver.isOptionExists('Option 111')).toBe(false);
});
it('should throw an error when trying to click on a non exists option', async () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
if (isAsync) {
await expect(driver.clickAtOption(20)).rejects.toThrow();
} else {
expect(() => driver.clickAtOption(20)).toThrow();
}
});
it('should focus on selected option', async () => {
const driver = createDriver(
<DropdownLayout
focusOnSelectedOption
visible
options={options}
selectedId={3}
/>,
);
expect(await driver.optionsScrollTop()).toBe(0);
});
it('should be visible and drop down', async () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(await driver.isShown()).toBe(true);
expect(await driver.isDown()).toBe(true);
expect(await driver.isUp()).toBe(false);
});
it('should be visible and drop up', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} dropDirectionUp />,
);
expect(await driver.isShown()).toBe(true);
expect(await driver.isDown()).toBe(false);
expect(await driver.isUp()).toBe(true);
});
it('should have all options values in dropdown list', async () => {
const _options = [
{ id: 0, value: 'Option 1' },
{ id: 1, value: 'Option 2' },
{ id: 2, value: 'Option 3' },
];
const optionsContent = _options.map(option => option.value);
const driver = createDriver(<DropdownLayout options={_options} />);
expect(await driver.optionsContent()).toEqual(optionsContent);
});
it('should hide dropdown on outside click', async () => {
const { driver, rerender } = render(
<DropdownLayout
onClickOutside={() =>
rerender(<DropdownLayout visible={false} options={options} />)
}
visible
options={options}
/>,
);
expect(await driver.isShown()).toBe(true);
await driver.mouseClickOutside();
expect(await driver.isShown()).toBe(false);
});
it('should have a default tab index', async () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(await driver.tabIndex()).toBe(0);
});
it('should have options', async () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(await driver.optionsLength()).toBe(7);
expect(await driver.optionContentAt(0)).toBe('Option 1');
expect(
await (await driver.optionByHook('dropdown-item-0')).isDivider(),
).toBe(false);
expect(await driver.isOptionADivider(4)).toBe(true);
expect(
await (await driver.optionByHook('dropdown-item-divider1')).isDivider(),
).toBe(true);
expect(await driver.optionContentAt(5)).toBe('Option 4');
expect(await driver.isOptionADivider(6)).toBe(true);
expect(
await (await driver.optionByHook('dropdown-item-6')).isDivider(),
).toBe(true);
});
it('should call onClose when esc key is pressed', async () => {
const onClose = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onClose={onClose} />,
);
await driver.mouseEnterAtOption(0);
await driver.pressEscKey();
expect(onClose).toBeCalled();
});
it('should click an option by value', async () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
await driver.clickAtOptionWithValue('Option 4');
expect(onSelect).toBeCalledWith(options[3], false);
});
describe('driver', () => {
it('should return correct number of options when use infiniteScroll', async () => {
const driver = createDriver(
<DropdownLayout
options={options}
infiniteScroll
loadMore={() => {}}
/>,
);
expect(await driver.optionsLength()).toBe(options.length);
});
it('should return correct array of options elements when use infiniteScroll', async () => {
const driver = createDriver(
<DropdownLayout
options={options}
infiniteScroll
loadMore={() => {}}
/>,
);
expect((await driver.options()).length).toBe(options.length);
});
it('should return correct content of options when use infiniteScroll', async () => {
const options = [
{ id: 0, value: 'Option 1' },
{ id: 1, value: 'Option 2' },
{ id: 2, value: 'Option 3' },
{ id: 3, value: 'Option 4' },
];
const driver = createDriver(
<DropdownLayout
options={options}
infiniteScroll
loadMore={() => {}}
/>,
);
expect(await driver.optionsContent()).toEqual(
options.map(option => option.value),
);
});
});
describe('onSelect', () => {
describe('with infiniteScroll', () => {
it('should call onSelect with true value when clicking on a selected option if infinite scroll enabled', async () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
infiniteScroll
loadMore={() => {}}
options={options}
onSelect={onSelect}
selectedId={0}
/>,
);
await driver.clickAtOption(0);
expect(onSelect).toBeCalledWith(options[0], true);
});
});
describe('with selectedId', () => {
it('should call onSelect with true value when clicking on a selected option', async () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
options={options}
onSelect={onSelect}
selectedId={0}
/>,
);
await driver.clickAtOption(0);
expect(onSelect).toBeCalledWith(options[0], true);
});
it('should call onSelect with false value when clicking on a selected option by hook', async () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
options={options}
onSelect={onSelect}
selectedId={0}
/>,
);
await (await driver.optionByHook('dropdown-item-3')).click();
expect(onSelect).toBeCalledWith(options[3], false);
});
});
describe('without selectedId', () => {
it('should notify a new option was selected for first selection', async () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
await driver.clickAtOption(0);
expect(onSelect).toBeCalledWith(options[0], false);
});
it('should notify a new option was selected after a value was previously selected', async () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
await driver.clickAtOption(0);
await driver.clickAtOption(1);
expect(onSelect).toHaveBeenLastCalledWith(options[1], false);
});
it('should notify the same option was selected', async () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
await driver.clickAtOption(0);
await driver.clickAtOption(0);
expect(onSelect).toHaveBeenLastCalledWith(options[0], true);
});
});
describe('keyboard events', () => {
it('should call onSelect when enter key is pressed', async () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
await driver.pressDownKey();
await driver.pressEnterKey();
expect(onSelect).toBeCalled();
});
it('should call onSelect when space key is pressed', async () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
await driver.pressDownKey();
await driver.pressSpaceKey();
expect(onSelect).toBeCalled();
});
it('should call onSelect when tab key is pressed', async () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
await driver.pressDownKey();
await driver.pressTabKey();
expect(onSelect).toBeCalled();
});
it('should not call onSelect when composing text via external means', async () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
await driver.pressEnterKey();
expect(onSelect).not.toBeCalled();
});
});
});
describe('render function', () => {
it('should be rendered with the option state', async () => {
const renderFunction = ({ id, disabled }) => ({
disabled,
value: jest.fn(),
id,
});
const options = [
renderFunction({ id: 0 }),
renderFunction({ id: 1 }),
renderFunction({ id: 2, disabled: true }),
];
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
await driver.clickAtOption(0);
expect(options[0].value).toHaveBeenCalledWith({
selected: true,
disabled: undefined,
hovered: false,
});
expect(options[1].value).toHaveBeenCalledWith({
selected: false,
disabled: undefined,
hovered: false,
});
await driver.mouseEnterAtOption(1);
expect(options[1].value).toHaveBeenCalledWith({
selected: false,
disabled: undefined,
hovered: true,
});
expect(options[2].value).toHaveBeenCalledWith({
selected: false,
disabled: true,
hovered: false,
});
});
});
describe('option content', () => {
it('should get the correct content when option is a node', async () => {
const options = [
{
id: 0,
value: (
<div>
<span style={{ color: 'brown' }}>Option 1</span>
</div>
),
},
{
id: 1,
value: (
<div>
<span style={{ color: 'brown' }}>Option 2</span>
</div>
),
disabled: true,
},
{ value: '-' },
{ id: 2, value: 'Option 3' },
{ id: 3, value: 'Option 4', disabled: true },
];
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
const optionsContent = await driver.optionsContent();
expect(optionsContent).toEqual(
expect.arrayContaining([
'Option 1',
'Option 2',
'',
'Option 3',
'Option 4',
]),
);
});
describe('overrideStyle and overrideOptionStyle', () => {
it('should get the correct content when option is a node', async () => {
const options = [
{
id: 0,
value: (
<div>
<span style={{ color: 'brown' }}>Option 1</span>
</div>
),
overrideStyle: true,
},
{
id: 1,
value: (
<div>
<span style={{ color: 'brown' }}>Option 2</span>
</div>
),
overrideStyle: true,
disabled: true,
},
{ value: '-' },
{
id: 3,
value: (
<div>
<span style={{ color: 'brown' }}>Option 3</span>
</div>
),
overrideOptionStyle: true,
},
{
id: 4,
value: (
<div>
<span style={{ color: 'brown' }}>Option 4</span>
</div>
),
overrideOptionStyle: true,
disabled: true,
},
];
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
const optionsContent = await driver.optionsContent();
expect(optionsContent).toEqual(
expect.arrayContaining([
'Option 1',
'Option 2',
'',
'Option 3',
'Option 4',
]),
);
});
});
});
it('should select the chosen value', async () => {
const selectedId = 0;
const driver = createDriver(
<DropdownLayout visible options={options} selectedId={selectedId} />,
);
expect(await driver.isOptionSelected(0)).toBe(true);
expect(
await (await driver.optionByHook('dropdown-item-0')).isSelected(),
).toBe(true);
});
it('should not select an option by default', async () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(await driver.getSelectedOptionId()).toBeNull();
});
it('should return the selected option', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} selectedId={0} />,
);
expect(await driver.getSelectedOptionId()).toEqual('0');
});
it('should return the selected option with string id', async () => {
const driver = createDriver(
<DropdownLayout
visible
options={[...options, { id: 'option', value: 'Option 4' }]}
selectedId="option"
/>,
);
expect(await driver.getSelectedOptionId()).toEqual('option');
});
it('should remember the selected option when getting re-opened after got closed', async () => {
const selectedId = 1;
const props = { visible: true, options, selectedId };
const { driver, rerender } = render(<DropdownLayout {...props} />);
expect(await driver.isOptionSelected(selectedId)).toBe(true);
rerender(<DropdownLayout {...props} visible={false} />);
rerender(<DropdownLayout {...props} visible />);
expect(await driver.isOptionSelected(selectedId)).toBe(true);
});
it('should select the chosen value when overrideStyle is true', async () => {
const selectedId = 0;
const _options = [{ id: 0, value: 'Option 1', overrideStyle: true }];
const driver = createDriver(
<DropdownLayout visible options={_options} selectedId={selectedId} />,
);
expect(await driver.isOptionSelected(0)).toBe(true);
});
it('should select the chosen value when label is provided', async () => {
const options = [{ id: 0, value: jest.fn(), label: 'Option Label' }];
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout onSelect={onSelect} visible options={options} />,
);
await driver.clickAtOption(0);
expect(onSelect).toBeCalledWith(options[0], false);
});
it('should not contain pointer arrow without the withArrow property', async () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(await driver.hasTopArrow()).toBe(false);
});
it('should contain pointer arrow when withArrow property is true', async () => {
const driver = createDriver(
<DropdownLayout visible withArrow options={options} />,
);
expect(await driver.hasTopArrow()).toBe(true);
});
it('should support mouse events', async () => {
const onMouseEnter = jest.fn();
const onMouseLeave = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
options={options}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>,
);
await driver.mouseEnter();
expect(onMouseEnter).toBeCalled();
expect(onMouseLeave).not.toBeCalled();
await driver.mouseLeave();
expect(onMouseLeave).toBeCalled();
});
describe('itemHeight prop', () => {
it('should be small by default', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
expect(await driver.isOptionHeightSmall(0)).toBe(true);
});
it('should be small', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} itemHeight="small" />,
);
expect(await driver.isOptionHeightSmall(0)).toBe(true);
});
it('should be big', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} itemHeight="big" />,
);
expect(await driver.isOptionHeightBig(0)).toBe(true);
});
});
describe('selectedHighlight prop', () => {
const selectedId = 0;
it('should be true by default', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} selectedId={selectedId} />,
);
const option = await driver.optionById(selectedId);
expect(await option.isSelected()).toBe(true);
});
describe('when true', () => {
it('should give the option a selected classname', async () => {
const driver = createDriver(
<DropdownLayout
selectedHighlight
visible
options={options}
selectedId={selectedId}
/>,
);
const option = await driver.optionById(selectedId);
expect(await option.isSelected()).toBe(true);
});
});
describe('when false', () => {
it('should not give the option a selected classname', async () => {
const driver = createDriver(
<DropdownLayout
selectedHighlight={false}
visible
options={options}
selectedId={selectedId}
/>,
);
const option = await driver.optionById(selectedId);
expect(await option.isSelected()).toBe(false);
});
});
});
describe('options that are links', () => {
it('should not be link by default', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
expect(await driver.isLinkOption(0)).toBe(false);
});
it('should be a link option', async () => {
const driver = createDriver(
<DropdownLayout
visible
options={options.map(opt => ({ ...opt, linkTo: 'http://wix.com' }))}
/>,
);
expect(await driver.isLinkOption(0)).toBe(true);
});
});
describe('onOptionMarked', () => {
it('should call onOptionMarked when option is hovered', async () => {
const onOptionMarked = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
options={options}
onOptionMarked={onOptionMarked}
/>,
);
await driver.mouseEnterAtOption(1);
expect(onOptionMarked).toBeCalledWith(options[1]);
});
it('should call onOptionMarked with null when mouse leaves a hovered option', async () => {
const onOptionMarked = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
options={options}
onOptionMarked={onOptionMarked}
/>,
);
await driver.mouseEnterAtOption(1);
await driver.mouseLeaveAtOption(1);
expect(onOptionMarked).toBeCalledWith(null);
expect(onOptionMarked).toHaveBeenCalledTimes(2);
});
it('should call onOptionMarked when down key is pressed', async () => {
const onOptionMarked = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
options={options}
onOptionMarked={onOptionMarked}
/>,
);
await driver.pressDownKey();
await driver.pressDownKey();
expect(onOptionMarked).toHaveBeenCalledTimes(2);
expect(onOptionMarked.mock.calls[0]).toEqual([options[0]]);
expect(onOptionMarked.mock.calls[1]).toEqual([options[1]]);
});
it('should call onOptionMarked with undefined when an option is selected', async () => {
const onOptionMarked = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
options={options}
onOptionMarked={onOptionMarked}
/>,
);
await driver.mouseEnterAtOption(1);
expect(onOptionMarked.mock.calls[0]).toEqual([options[1]]);
await driver.clickAtOption(1);
expect(onOptionMarked.mock.calls[1]).toBeUndefined();
});
it('should call onOptionMarked with null when options are closed', async () => {
const onOptionMarked = jest.fn();
const { driver, rerender } = render(
<DropdownLayout
visible
options={options}
onOptionMarked={onOptionMarked}
/>,
);
await driver.mouseEnterAtOption(1);
expect(onOptionMarked.mock.calls[0]).toEqual([options[1]]);
rerender(
<DropdownLayout
visible={false}
options={options}
onOptionMarked={onOptionMarked}
/>,
);
expect(onOptionMarked.mock.calls[1]).toEqual([null]);
});
it('should call onOptionMarked with undefined when options change and the marked option is removed', async () => {
const onOptionMarked = jest.fn();
const { driver, rerender } = render(
<DropdownLayout
visible
options={options}
onOptionMarked={onOptionMarked}
/>,
);
await driver.mouseEnterAtOption(1);
expect(onOptionMarked.mock.calls[0]).toEqual([options[1]]);
rerender(
<DropdownLayout
visible
options={options.slice(2)}
onOptionMarked={onOptionMarked}
/>,
);
expect(onOptionMarked.mock.calls[1]).toEqual([null]);
});
});
describe('controlled and uncontrolled logic', () => {
describe('controlled', () => {
it('should work as a controlled component when selectedId an onSelect are given', async () => {
// give selectedId and onSelect
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
options={options}
onSelect={onSelect}
selectedId={0}
/>,
);
// select item
await driver.clickAtOption(1);
// expect internal state to not change
expect(await driver.isOptionSelected(0)).toBe(true);
});
});
describe('uncontrolled', () => {
it('should work as an uncontrolled component when only selectedId is supplied', async () => {
// give selectedId
const driver = createDriver(
<DropdownLayout visible options={options} selectedId={0} />,
);
// select item
await driver.clickAtOption(1);
// expect internal state to change
expect(await driver.isOptionSelected(1)).toBe(true);
});
it('should work as an uncontrolled component when only onSelect is supplied', async () => {
// give onSelect
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={jest.fn()} />,
);
// select item
await driver.clickAtOption(1);
// expect internal state to change
expect(await driver.isOptionSelected(1)).toBe(true);
});
});
});
describe('hover logic', () => {
it('should not hover any option by default', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
expect(
await Promise.all(
options.map((option, index) => driver.isOptionHovered(index)),
),
).not.toContain(true);
});
it('should hover starting from the top', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
await driver.pressDownKey();
expect(await driver.isOptionHovered(0)).toBe(true);
});
it('should hover starting from the selected item', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
await driver.clickAtOption(0);
await driver.pressDownKey();
expect(await driver.isOptionHovered(1)).toBe(true);
});
it('should hover when mouse enter and unhover when mouse leave', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
await driver.mouseEnterAtOption(0);
expect(await driver.isOptionHovered(0)).toBe(true);
await driver.mouseLeaveAtOption(0);
expect(await driver.isOptionHovered(0)).toBe(false);
});
it('should hover when mouse enter and unhover when mouse leave by data hook', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
const option = await driver.optionByHook('dropdown-item-0');
await option.mouseEnter();
expect(await option.isHovered()).toBe(true);
await option.mouseLeave();
expect(await option.isHovered()).toBe(false);
});
it('should hover when mouse enter and unhover when mouse leave when overrideStyle is true', async () => {
const _options = [{ id: 0, value: 'Option 1', overrideStyle: true }];
const driver = createDriver(
<DropdownLayout visible options={_options} />,
);
await driver.mouseEnterAtOption(0);
expect(await driver.isOptionHovered(0)).toBe(true);
await driver.mouseLeaveAtOption(0);
expect(await driver.isOptionHovered(0)).toBe(false);
});
it('should not hover divider or a disabled item when mouse enter', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
await driver.mouseEnterAtOption(2);
expect(await driver.isOptionHovered(2)).toBe(false);
await driver.mouseLeaveAtOption(4);
expect(await driver.isOptionHovered(4)).toBe(false);
});
it('should have only one hovered option', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
await driver.mouseEnterAtOption(0);
expect(await driver.isOptionHovered(0)).toBe(true);
await driver.mouseEnterAtOption(1);
expect(await driver.isOptionHovered(0)).toBe(false);
expect(await driver.isOptionHovered(1)).toBe(true);
});
it('should hovered items cyclic and skipping divider or disabled items on down key', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
await driver.pressDownKey();
await driver.pressDownKey();
expect(await driver.isOptionHovered(1)).toBe(true);
await driver.pressDownKey();
expect(await driver.isOptionHovered(3)).toBe(true);
await driver.pressDownKey();
expect(await driver.isOptionHovered(5)).toBe(true);
await driver.pressDownKey();
expect(await driver.isOptionHovered(0)).toBe(true);
});
it('should hovered items cyclic and skipping divider or disabled on up key', async () => {
const driver = createDriver(
<DropdownLayout visible options={options} />,
);
await driver.pressUpKey();
expect(await driver.isOptionHovered(5)).toBe(true);
await driver.pressUpKey();
expect(await driver.isOptionHovered(3)).toBe(true);
await driver.pressUpKey();
expect(await driver.isOptionHovered(1)).toBe(true);
await driver.pressUpKey();
expect(await driver.isOptionHovered(0)).toBe(true);
});
it('should hover starting from a given item', async () => {
const _options = [
{ id: 10, value: 'Option 1' },
{ id: 20, value: 'Option 2' },
{ id: 30, value: 'Option 3' },
];
const driver = createDriver(
<DropdownLayout
visible
options={_options}
selectedId={20}
onSelect={jest.fn()}
/>,
);
await driver.pressDownKey();
expect(await driver.isOptionHovered(2)).toBe(true);
});
it('should remember the hovered option when options change', async () => {
const _options = [
{ id: 0, value: 'a 1' },
{ id: 1, value: 'a 2' },
{ id: 2, value: 'a 3' },
{ id: 3, value: 'a 4' },
];
const { driver, rerender } = render(
<DropdownLayout visible options={_options} />,
);
await driver.pressDownKey();
await driver.pressDownKey();
await driver.pressDownKey();
await driver.pressDownKey();
expect(await driver.isOptionHovered(3)).toBe(true);
rerender(<DropdownLayout visible options={_options.slice(1)} />);
expect(await driver.isOptionHovered(2)).toBe(true);
});
it('should reset the hovered option when options change and hovered option does not exist anymore', async () => {
const initialOptions = [
{ id: 0, value: 'a 1' },
{ id: 1, value: 'a 2' },
{ id: 2, value: 'a 3' },
{ id: 3, value: 'a 4' },
];
const { driver, rerender } = render(
<DropdownLayout visible options={initialOptions} />,
);
await driver.pressDownKey();
await driver.pressDownKey();
expect(await driver.isOptionHovered(1)).toBe(true);
rerender(<DropdownLayout visible options={initialOptions.slice(2)} />);
expect(await driver.isOptionHovered(0)).toBe(false);
expect(await driver.isOptionHovered(1)).toBe(false);
});
});
describe('option validator', () => {
describe('valid', () => {
it('option', async () => {
render(<DropdownLayout options={[{ id: '1', value: 'hello' }]} />);
expect(consoleErrors.get()).toHaveLength(0);
});
it('option with function value', async () => {
render(<DropdownLayout options={[{ id: '1', value: () => {} }]} />);
expect(consoleErrors.get()).toHaveLength(0);
});
it('divider', async () => {
render(
<DropdownLayout options={[{ value: DIVIDER_OPTION_VALUE }]} />,
);
expect(consoleErrors.get()).toHaveLength(0);
});
});
});
describe('option markedOption', () => {
const initialOptions = [
{ id: 0, value: 'a 1' },
{ id: 1, value: 'a 2' },
{ id: 2, value: 'a 3' },
{ id: 3, value: 'a 4' },
];
it('should not mark any option by default', async () => {
const { driver } = render(<DropdownLayout options={initialOptions} />);
expect(await driver.markedOption()).toBe(null);
});
it('should mark first option when equals true', async () => {
const { driver } = render(
<DropdownLayout options={initialOptions} markedOption />,
);
expect(await driver.markedOption()).toBe('a 1');
});
it('should mark second option when providing id', async () => {
const { driver } = render(
<DropdownLayout options={initialOptions} markedOption={1} />,
);
expect(await driver.markedOption()).toBe('a 2');
});
it('should call onOptionMarked on default options', async () => {
const spyOnOptionMarked = jest.fn();
const { driver } = render(
<DropdownLayout
options={initialOptions}
markedOption
onOptionMarked={spyOnOptionMarked}
/>,
);
expect(await driver.markedOption()).toBe('a 1');
expect(spyOnOptionMarked).toHaveBeenLastCalledWith(initialOptions[0]);
});
});
describe('option focusOnOption', () => {
const initialOptions = [
{ id: 0, value: 'a 1' },
{ id: 1, value: 'a 2' },
{ id: 2, value: 'a 3' },
{ id: 3, value: 'a 4' },
];
const component = props => (
<DropdownLayout options={initialOptions} {...props} />
);
it('should not mark any option by default', async () => {
const { driver } = render(component());
expect(await driver.markedOption()).toBe(null);
});
it('should mark fourth option when providing id', async () => {
const { driver, rerender } = render(component({ focusOnOption: 1 }));
expect(await driver.markedOption()).toBe('a 2');
const setProps = props => rerender(component(props));
setProps({ focusOnOption: 3 });
expect(await driver.markedOption()).toBe('a 4');
});
it('should call onOptionMarked when focusing an option', async () => {
const spyOnOptionMarked = jest.fn();
const initialProps = {
focusOnOption: 1,
onOptionMarked: spyOnOptionMarked,
};
const { driver, rerender } = render(component(initialProps));
const setProps = props => rerender(component(props));
setProps({ ...initialProps, focusOnOption: 3 });
expect(await driver.markedOption()).toBe('a 4');
expect(spyOnOptionMarked).toHaveBeenLastCalledWith(initialOptions[3]);
});
it('should not mark any option when providing id is null', async () => {
const spyOnOptionMarked = jest.fn();
const { driver, rerender } = render(
component({ focusOnOption: 1, onOptionMarked: spyOnOptionMarked }),
);
const setProps = props => rerender(component(props));
setProps({ focusOnOption: undefined });
expect(await driver.markedOption()).toBe(null);
// Should be called only once and not a second time after removing the focus.
expect(spyOnOptionMarked).toHaveBeenCalledTimes(1);
});
});
}
// These tests are not driver related, so no need to test them both sync and async
describe('invalid', () => {
const render = rawRender;
let consoleErrorSpy;
beforeEach(() => {
consoleErrorSpy = jest
.spyOn(global.console, 'error')
.mockImplementation(jest.fn());
});
afterEach(() => {
consoleErrorSpy.mockRestore();
});
it('no value', async () => {
render(<DropdownLayout options={[{ id: '1' }]} />);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toBeCalledWith(
expect.stringContaining('option.value'),
);
});
it('no id and not divider', async () => {
render(<DropdownLayout options={[{ value: 'aaa' }]} />);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toBeCalledWith(
expect.stringContaining('option.id'),
);
});
it('empty trimmed id', async () => {
render(<DropdownLayout options={[{ id: ' ', value: 'hello' }]} />);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toBeCalledWith(
expect.stringContaining('option.id'),
);
});
it('empty trimmed value', async () => {
render(<DropdownLayout options={[{ id: '1', value: ' ' }]} />);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toBeCalledWith(
expect.stringContaining('option.value'),
);
});
});
});