wix-style-react
Version:
wix-style-react
673 lines (592 loc) • 22.5 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 { createRendererWithDriver, cleanup } from '../../test/utils/react';
describe('DropdownLayout', () => {
const render = createRendererWithDriver(dropdownLayoutDriverFactory);
const createDriver = jsx => render(jsx).driver;
afterEach(() => {
cleanup();
});
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: '-' },
];
it('should have be invisible and drop down by default', () => {
const driver = createDriver(<DropdownLayout options={options} />);
expect(driver.isShown()).toBeFalsy();
expect(driver.isDown()).toBeTruthy();
});
it('should throw an error when trying to click on a non exists option', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(() => driver.clickAtOption(20)).toThrow();
});
it('should focus on selected option', () => {
const driver = createDriver(
<DropdownLayout
focusOnSelectedOption
visible
options={options}
selectedId={3}
/>,
);
expect(driver.optionsScrollTop()).toBe(0);
});
it('should be visible and drop down', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(driver.isShown()).toBeTruthy();
expect(driver.isDown()).toBeTruthy();
});
it('should have all options values in dropdown list', () => {
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(driver.optionsContent()).toEqual(optionsContent);
});
it('should hide dropdown on outside click', () => {
const { driver, rerender } = render(
<DropdownLayout
onClickOutside={() =>
rerender(<DropdownLayout visible={false} options={options} />)
}
visible
options={options}
/>,
);
expect(driver.isShown()).toBeTruthy();
driver.mouseClickOutside();
expect(driver.isShown()).toBeFalsy();
});
it('should have a default tab index', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(driver.tabIndex()).toBe(0);
});
it('should have options', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(driver.optionsLength()).toBe(7);
expect(driver.optionContentAt(0)).toBe('Option 1');
expect(driver.isOptionADivider(4)).toBeTruthy();
expect(
driver.optionByHook('dropdown-divider-divider1').isDivider(),
).toBeTruthy();
expect(driver.optionContentAt(5)).toBe('Option 4');
expect(driver.isOptionADivider(6)).toBeTruthy();
expect(driver.optionByHook('dropdown-divider-6').isDivider()).toBeTruthy();
});
it('should call onClose when esc key is pressed', () => {
const onClose = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onClose={onClose} />,
);
driver.mouseEnterAtOption(0);
driver.pressEscKey();
expect(onClose).toBeCalled();
});
it('should click an option by value', () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
driver.clickAtOptionWithValue('Option 4');
expect(onSelect).toBeCalledWith(options[3], false);
});
describe('onSelect', () => {
describe('with selectedId', () => {
it('should call onSelect with true value when clicking on a selected option', () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
options={options}
onSelect={onSelect}
selectedId={0}
/>,
);
driver.clickAtOption(0);
expect(onSelect).toBeCalledWith(options[0], true);
});
it('should call onSelect with false value when clicking on a selected option by hook', () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
options={options}
onSelect={onSelect}
selectedId={0}
/>,
);
driver.optionByHook('dropdown-item-3').click();
expect(onSelect).toBeCalledWith(options[3], false);
});
});
describe('without selectedId', () => {
it('should nofity a new option was selected for first selection', () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
driver.clickAtOption(0);
expect(onSelect).toBeCalledWith(options[0], false);
});
it('should nofity a new option was selected after a value was previously selected', () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
driver.clickAtOption(0);
driver.clickAtOption(1);
expect(onSelect).toHaveBeenLastCalledWith(options[1], false);
});
it('should nofity the same option was selected', () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
driver.clickAtOption(0);
driver.clickAtOption(0);
expect(onSelect).toHaveBeenLastCalledWith(options[0], true);
});
});
describe('keyboard events', () => {
it('should call onSelect when enter key is pressed', () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
driver.pressDownKey();
driver.pressEnterKey();
expect(onSelect).toBeCalled();
});
it('should call onSelect when space key is pressed', () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
driver.pressDownKey();
driver.pressSpaceKey();
expect(onSelect).toBeCalled();
});
it('should call onSelect when tab key is pressed', () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
driver.pressDownKey();
driver.pressTabKey();
expect(onSelect).toBeCalled();
});
it('should not call onSelect when composing text via external means', () => {
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={onSelect} />,
);
driver.pressEnterKey();
expect(onSelect).not.toBeCalled();
});
});
});
it('should render a function option with the rendered item props', () => {
const selectedId = 0;
const unSelectedId = 1;
const optionsWithFuncValues = [
{
id: 0,
value: ({ selected }) => (
<div>option {selected ? 'selected' : 'not selected'}</div>
),
},
{
id: 1,
value: ({ selected }) => (
<div>option {selected ? 'selected' : 'not selected'}</div>
),
},
];
const driver = createDriver(
<DropdownLayout
visible
options={optionsWithFuncValues}
selectedId={selectedId}
/>,
);
expect(driver.optionContentAt(selectedId)).toEqual('option selected');
expect(driver.optionContentAt(unSelectedId)).toEqual('option not selected');
});
it('should select the chosen value', () => {
const selectedId = 0;
const driver = createDriver(
<DropdownLayout visible options={options} selectedId={selectedId} />,
);
expect(driver.isOptionSelected(0)).toBeTruthy();
expect(driver.optionByHook('dropdown-item-0').isSelected()).toBeTruthy();
});
it('should remember the selected option when getting re-opened after got closed', () => {
const selectedId = 1;
const props = { visible: true, options, selectedId };
const { driver, rerender } = render(<DropdownLayout {...props} />);
expect(driver.isOptionSelected(selectedId)).toBeTruthy();
rerender(<DropdownLayout {...props} visible={false} />);
rerender(<DropdownLayout {...props} visible />);
expect(driver.isOptionSelected(selectedId)).toBeTruthy();
});
it('should select the chosen value when overrideStyle is true', () => {
const selectedId = 0;
const _options = [{ id: 0, value: 'Option 1', overrideStyle: true }];
const driver = createDriver(
<DropdownLayout visible options={_options} selectedId={selectedId} />,
);
expect(driver.isOptionSelectedWithGlobalClassName(0)).toBeTruthy();
expect(
driver.optionByHook('dropdown-item-0').isSelectedWithGlobalClassName(),
).toBeTruthy();
});
it('should not contain pointer arrow without the withArrow property', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(driver.hasTopArrow()).toBeFalsy();
});
it('should contain pointer arrow when withArrow property is true', () => {
const driver = createDriver(
<DropdownLayout visible withArrow options={options} />,
);
expect(driver.hasTopArrow()).toBeTruthy();
});
it('should support mouse events', () => {
const onMouseEnter = jest.fn();
const onMouseLeave = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
options={options}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>,
);
driver.mouseEnter();
expect(onMouseEnter).toBeCalled();
expect(onMouseLeave).not.toBeCalled();
driver.mouseLeave();
expect(onMouseLeave).toBeCalled();
});
describe('itemHeight prop', () => {
it('should be small by default', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(driver.isOptionHeightSmall(0)).toBe(true);
});
it('should be small', () => {
const driver = createDriver(
<DropdownLayout visible options={options} itemHeight="small" />,
);
expect(driver.isOptionHeightSmall(0)).toBe(true);
});
it('should be big', () => {
const driver = createDriver(
<DropdownLayout visible options={options} itemHeight="big" />,
);
expect(driver.isOptionHeightBig(0)).toBe(true);
});
});
describe('selectedHighlight prop', () => {
const selectedId = 0;
it('should be true by default', () => {
const driver = createDriver(
<DropdownLayout visible options={options} selectedId={selectedId} />,
);
expect(driver.optionById(selectedId).isSelected()).toBe(true);
});
describe('when true', () => {
it('should give the option a selected classname', () => {
const driver = createDriver(
<DropdownLayout
selectedHighlight
visible
options={options}
selectedId={selectedId}
/>,
);
expect(driver.optionById(selectedId).isSelected()).toBeTruthy();
});
});
describe('when false', () => {
it('should not give the option a selected classname', () => {
const driver = createDriver(
<DropdownLayout
selectedHighlight={false}
visible
options={options}
selectedId={selectedId}
/>,
);
expect(driver.optionById(selectedId).isSelected()).toBeFalsy();
});
});
});
describe('options that are links', () => {
it('should not be link by default', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(driver.isLinkOption(0)).toBe(false);
});
it('should be a link option', () => {
const driver = createDriver(
<DropdownLayout
visible
options={options.map(opt => ({ ...opt, linkTo: 'http://wix.com' }))}
/>,
);
expect(driver.isLinkOption(0)).toBe(true);
});
});
describe('controlled and uncontrolled logic', () => {
describe('controlled', () => {
it('should work as a controlled component when selectedId an onSelect are given', () => {
//give selectedId and onSelect
const onSelect = jest.fn();
const driver = createDriver(
<DropdownLayout
visible
options={options}
onSelect={onSelect}
selectedId={0}
/>,
);
//select item
driver.clickAtOption(1);
//expect internal state to not change
expect(driver.isOptionSelected(0)).toBeTruthy();
});
});
describe('uncontrolled', () => {
it('should work as an uncontrolled component when only selectedId is supplied', () => {
//give selectedId
const driver = createDriver(
<DropdownLayout visible options={options} selectedId={0} />,
);
//select item
driver.clickAtOption(1);
//expect internal state to change
expect(driver.isOptionSelected(1)).toBeTruthy();
});
it('should work as an uncontrolled component when only onSelect is supplied', () => {
//give onSelect
const driver = createDriver(
<DropdownLayout visible options={options} onSelect={jest.fn()} />,
);
//select item
driver.clickAtOption(1);
//expect internal state to change
expect(driver.isOptionSelected(1)).toBeTruthy();
});
});
});
describe('hover logic', () => {
it('should not hover any option by default', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
expect(
options.map((option, index) => driver.isOptionHovered(index)),
).not.toContain(true);
});
it('should hover starting from the top', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
driver.pressDownKey();
expect(driver.isOptionHovered(0)).toBeTruthy();
});
it('should hover starting from the selected item', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
driver.clickAtOption(0);
driver.pressDownKey();
expect(driver.isOptionHovered(1)).toBeTruthy();
});
it('should hover when mouse enter and unhover when mouse leave', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
driver.mouseEnterAtOption(0);
expect(driver.isOptionHovered(0)).toBeTruthy();
driver.mouseLeaveAtOption(0);
expect(driver.isOptionHovered(0)).toBeFalsy();
});
it('should hover when mouse enter and unhover when mouse leave by data hook', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
const option = driver.optionByHook('dropdown-item-0');
option.mouseEnter();
expect(option.isHovered()).toBeTruthy();
option.mouseLeave();
expect(option.isHovered()).toBeFalsy();
});
it('should hover when mouse enter and unhover when mouse leave when overrideStyle is true', () => {
const _options = [{ id: 0, value: 'Option 1', overrideStyle: true }];
const driver = createDriver(
<DropdownLayout visible options={_options} />,
);
driver.mouseEnterAtOption(0);
expect(driver.isOptionHoveredWithGlobalClassName(0)).toBeTruthy();
expect(
driver.optionByHook('dropdown-item-0').isHoveredWithGlobalClassName(),
).toBeTruthy();
driver.mouseLeaveAtOption(0);
expect(driver.isOptionHoveredWithGlobalClassName(0)).toBeFalsy();
expect(
driver.optionByHook('dropdown-item-0').isHoveredWithGlobalClassName(),
).toBeFalsy();
});
it('should not hover divider or a disabled item when mouse enter', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
driver.mouseEnterAtOption(2);
expect(driver.isOptionHovered(2)).toBeFalsy();
driver.mouseLeaveAtOption(4);
expect(driver.isOptionHovered(4)).toBeFalsy();
});
it('should have only one hovered option', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
driver.mouseEnterAtOption(0);
expect(driver.isOptionHovered(0)).toBeTruthy();
driver.mouseEnterAtOption(1);
expect(driver.isOptionHovered(0)).toBeFalsy();
expect(driver.isOptionHovered(1)).toBeTruthy();
});
it('should hovered items cyclic and skipping divider or disabled items on down key', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
driver.pressDownKey();
driver.pressDownKey();
expect(driver.isOptionHovered(1)).toBeTruthy();
driver.pressDownKey();
expect(driver.isOptionHovered(3)).toBeTruthy();
driver.pressDownKey();
expect(driver.isOptionHovered(5)).toBeTruthy();
driver.pressDownKey();
expect(driver.isOptionHovered(0)).toBeTruthy();
});
it('should hovered items cyclic and skipping divider or disabled on up key', () => {
const driver = createDriver(<DropdownLayout visible options={options} />);
driver.pressUpKey();
expect(driver.isOptionHovered(5)).toBeTruthy();
driver.pressUpKey();
expect(driver.isOptionHovered(3)).toBeTruthy();
driver.pressUpKey();
expect(driver.isOptionHovered(1)).toBeTruthy();
driver.pressUpKey();
expect(driver.isOptionHovered(0)).toBeTruthy();
});
it('should hover starting from a given item', () => {
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()}
/>,
);
driver.pressDownKey();
expect(driver.isOptionHovered(2)).toBeTruthy();
});
it('should remember the hovered option when options change', () => {
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} />,
);
driver.pressDownKey();
driver.pressDownKey();
driver.pressDownKey();
driver.pressDownKey();
expect(driver.isOptionHovered(3)).toBeTruthy();
rerender(<DropdownLayout visible options={_options.slice(1)} />);
expect(driver.isOptionHovered(2)).toBeTruthy();
});
it('should reset the hovered option when options change and hovered option does not exist anymore', () => {
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} />,
);
driver.pressDownKey();
driver.pressDownKey();
expect(driver.isOptionHovered(1)).toBeTruthy();
rerender(<DropdownLayout visible options={initialOptions.slice(2)} />);
expect(driver.isOptionHovered(0)).toBeFalsy();
expect(driver.isOptionHovered(1)).toBeFalsy();
});
});
describe('theme support', () => {
it('should allow setting a custom theme', () => {
const props = { theme: 'material', options };
const { driver } = render(<DropdownLayout {...props} />);
expect(driver.hasTheme('material')).toBe(true);
});
});
describe('option validator', () => {
describe('valid', () => {
it('option', () => {
render(<DropdownLayout options={[{ id: '1', value: 'hello' }]} />);
expect(consoleErrors.get()).toHaveLength(0);
});
it('option with function value', () => {
render(<DropdownLayout options={[{ id: '1', value: () => {} }]} />);
expect(consoleErrors.get()).toHaveLength(0);
});
it('divider', () => {
render(<DropdownLayout options={[{ value: DIVIDER_OPTION_VALUE }]} />);
expect(consoleErrors.get()).toHaveLength(0);
});
});
describe('invalid', () => {
let consoleErrorSpy;
beforeEach(() => {
consoleErrorSpy = jest
.spyOn(global.console, 'error')
.mockImplementation(jest.fn());
});
afterEach(() => {
consoleErrorSpy.mockRestore();
});
it('no value', () => {
render(<DropdownLayout options={[{ id: '1' }]} />);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toBeCalledWith(
expect.stringContaining('option.value'),
);
});
it('no id and not divider', () => {
render(<DropdownLayout options={[{ value: 'aaa' }]} />);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toBeCalledWith(
expect.stringContaining('option.id'),
);
});
it('empty trimmed id', () => {
render(<DropdownLayout options={[{ id: ' ', value: 'hello' }]} />);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toBeCalledWith(
expect.stringContaining('option.id'),
);
});
it('empty trimmed value', () => {
render(<DropdownLayout options={[{ id: '1', value: ' ' }]} />);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toBeCalledWith(
expect.stringContaining('option.value'),
);
});
});
});
});