wix-style-react
Version:
810 lines (639 loc) • 26.6 kB
JavaScript
import React from 'react';
import times from '../../utils/operators/times';
import ModalSelectorLayout from '../ModalSelectorLayout';
import modalSelectorLayoutDriverFactory from '../ModalSelectorLayout.driver';
import { ASSET_PREFIX } from '../../../test/utils';
import {
cleanup,
createRendererWithDriver,
createRendererWithUniDriver,
} from '../../../test/utils/react';
import { modalSelectorLayoutUniDriverFactory } from '../ModalSelectorLayout.uni.driver';
import { eventually } from '../../../test/utils/unit';
// TODO: remove this hack
// taken from here: https://github.com/facebook/jest/issues/2157#issuecomment-279171856
const flushPromises = () => new Promise(resolve => setImmediate(resolve));
const paginatedDataSourceFactory = (items, timeout) => (
searchQuery,
offset,
limit,
) => {
const filteredItems = items.filter(({ title }) =>
title.includes(searchQuery),
);
const data = {
items: filteredItems.slice(offset, offset + limit),
totalCount: filteredItems.length,
};
return timeout
? new Promise(resolve => {
setTimeout(() => resolve(data), timeout);
})
: Promise.resolve(data);
};
const paginatedDataSource = paginatedDataSourceFactory(
times(7, i => ({ id: i, title: `title-${i}`, image: <img /> })),
);
const paginatedDataSourceWithTimeout = paginatedDataSourceFactory(
times(7, i => ({ id: i, title: `title-${i}`, image: <img /> })),
100,
);
const emptyDataSource = paginatedDataSourceFactory([]);
const requiredProps = {
dataSource: emptyDataSource,
};
describe('ModalSelectorLayout', () => {
describe('[sync]', () => {
runTests(createRendererWithDriver(modalSelectorLayoutDriverFactory));
});
describe('[async]', () => {
runTests(createRendererWithUniDriver(modalSelectorLayoutUniDriverFactory));
});
function runTests(render) {
afterEach(() => cleanup());
const createDriver = props =>
render(<ModalSelectorLayout {...requiredProps} {...props} />).driver;
describe('layout', () => {
it('should show medium loader', async () => {
jest.useFakeTimers();
const driver = createDriver({
dataSource: paginatedDataSourceFactory([], 10),
});
expect(await driver.mainLoaderDriver().exists()).toBe(true);
expect(await driver.mainLoaderDriver().isMedium()).toBe(true);
await jest.runOnlyPendingTimers();
});
it('should disable "OK" button while loading', async () => {
const driver = createDriver();
expect(await driver.okButtonDriver().isButtonDisabled()).toBe(true);
});
it('should hide search while loading', async () => {
const driver = createDriver();
expect(await driver.searchDriver().exists()).toBe(false);
});
it('should hide the loader & render only passed empty state when there are no items in data source', async () => {
const driver = createDriver({
dataSource: emptyDataSource,
emptyState: <img src="empty_state.png" />,
});
await flushPromises();
expect(await driver.mainLoaderDriver().exists()).toBe(false);
expect(await driver.showsNoResultsFoundState()).toBe(false);
expect(await driver.searchDriver().exists()).toBe(false);
expect(await driver.showsEmptyState()).toBe(true);
expect(await driver.getEmptyState()).toBeInstanceOf(HTMLImageElement);
expect((await driver.getEmptyState()).src).toBe(
`${ASSET_PREFIX}empty_state.png`,
);
expect(await driver.listExists()).toBe(false);
});
it('should hide loader & render the list of items with images, when there are items in data source', async () => {
const dataSource = paginatedDataSourceFactory([
{
id: 1,
title: 'rick',
subtitle: 'sanchez',
extraText: 'get',
image: <img src="rick.png" />,
},
{
id: 2,
title: 'morty',
subtitle: 'smith',
extraNode: <img src="shwifty.png" />,
image: <img src="morty.png" />,
},
]);
const driver = createDriver({ dataSource });
await flushPromises();
expect(await driver.mainLoaderDriver().exists()).toBe(false);
expect(await driver.showsEmptyState()).toBe(false);
expect(await driver.listExists()).toBe(true);
expect(
await driver.getSelectorDriverAt(0).titleTextDriver().getText(),
).toBe('rick');
expect(
await driver.getSelectorDriverAt(0).subtitleTextDriver().getText(),
).toBe('sanchez');
expect(
(await driver.getSelectorDriverAt(0).getExtraNode()).textContent,
).toBe('get');
expect(await driver.getSelectorDriverAt(0).getImage()).toBeInstanceOf(
HTMLImageElement,
);
expect((await driver.getSelectorDriverAt(0).getImage()).src).toBe(
`${ASSET_PREFIX}rick.png`,
);
expect(
await driver.getSelectorDriverAt(1).titleTextDriver().getText(),
).toBe('morty');
expect(
await driver.getSelectorDriverAt(1).subtitleTextDriver().getText(),
).toBe('smith');
expect(
await driver.getSelectorDriverAt(1).getExtraNode(),
).toBeInstanceOf(HTMLImageElement);
expect((await driver.getSelectorDriverAt(1).getExtraNode()).src).toBe(
`${ASSET_PREFIX}shwifty.png`,
);
expect(await driver.getSelectorDriverAt(1).getImage()).toBeInstanceOf(
HTMLImageElement,
);
expect((await driver.getSelectorDriverAt(1).getImage()).src).toBe(
`${ASSET_PREFIX}morty.png`,
);
});
});
describe('texts & callbacks', () => {
it('should allow setting title', async () => {
const expectedTitle = 'Wubba Lubba Dub Dub';
const driver = createDriver({ title: expectedTitle });
expect(await driver.getTitle()).toBe(expectedTitle);
});
it('should allow setting subtitle', async () => {
const expectedSubtitle = 'Wubba Lubba Dub Dub';
const driver = createDriver({
dataSource: paginatedDataSource,
subtitle: expectedSubtitle,
});
await flushPromises();
expect(await driver.subtitleTextDriver().getText()).toBe(
expectedSubtitle,
);
});
it('should call "onClose" when clicking on X icon', async () => {
const stub = jest.fn();
const driver = createDriver({ onClose: stub });
await driver.clickOnClose();
expect(stub).toHaveBeenCalled();
});
it('should allow setting "Cancel" button text', async () => {
const expectedTitle = 'Wubba Lubba Dub Dub';
const driver = createDriver({
cancelButtonText: expectedTitle,
});
expect(await driver.cancelButtonDriver().getButtonTextContent()).toBe(
expectedTitle,
);
});
it('should call "onCancel" when clicking on "Cancel" icon', async () => {
const stub = jest.fn();
const driver = createDriver({
cancelButtonText: 'Cancel',
onCancel: stub,
});
await driver.cancelButtonDriver().click();
expect(stub).toHaveBeenCalled();
});
it('should allow setting "OK" button text', async () => {
const expectedTitle = 'Wubba Lubba Dub Dub';
const driver = createDriver({ okButtonText: expectedTitle });
expect(await driver.okButtonDriver().getButtonTextContent()).toBe(
expectedTitle,
);
});
});
describe('search', () => {
it('should render search input after the items are loaded', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
});
await flushPromises();
expect(await driver.searchDriver().exists()).toBe(true);
});
it('should allow hiding search', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
withSearch: false,
});
await flushPromises();
expect(await driver.searchDriver().exists()).toBe(false);
});
it('should allow passing placeholder', async () => {
const expectedPlaceholder = 'some placeholder';
const driver = createDriver({
dataSource: paginatedDataSource,
searchPlaceholder: expectedPlaceholder,
});
await flushPromises();
expect(await driver.searchDriver().inputDriver.getPlaceholder()).toBe(
expectedPlaceholder,
);
});
it('should show medium loader, and then show filtered items', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
});
await flushPromises();
eventually(async () =>
expect(await driver.mainLoaderDriver().exists()).toBe(true),
);
await driver.searchDriver().inputDriver.focus();
await driver.searchDriver().inputDriver.enterText('title-1');
await flushPromises();
await driver.scrollDown();
await eventually(async () =>
expect(await driver.mainLoaderDriver().exists()).toBe(false),
);
expect(await driver.numberOfItemsInList()).toBe(1);
expect(
await driver.getSelectorDriverAt(0).titleTextDriver().getText(),
).toBe('title-1');
});
it('should render noResultsFoundState with current search value only if no results were found', async () => {
jest.useFakeTimers();
const searchValue = 'wubba lubba dub dub';
const driver = createDriver({
dataSource: paginatedDataSourceWithTimeout,
noResultsFoundStateFactory: _searchValue => (
<img alt={_searchValue} src="no-results-found.png" />
),
});
expect(await driver.showsNoResultsFoundState()).toBe(false);
await jest.runOnlyPendingTimers();
expect(await driver.showsNoResultsFoundState()).toBe(false);
await driver.searchDriver().inputDriver.focus();
await driver.searchDriver().inputDriver.enterText(searchValue);
expect(await driver.showsNoResultsFoundState()).toBe(false);
await jest.runOnlyPendingTimers();
expect(await driver.showsNoResultsFoundState()).toBe(true);
expect(await driver.getNoResultsFoundState()).toBeInstanceOf(
HTMLImageElement,
);
expect((await driver.getNoResultsFoundState()).src).toBe(
`${ASSET_PREFIX}no-results-found.png`,
);
expect((await driver.getNoResultsFoundState()).alt).toBe(searchValue);
await driver.searchDriver().inputDriver.clickClear();
await jest.runOnlyPendingTimers();
expect(await driver.showsNoResultsFoundState()).toBe(false);
});
it('should clear search on clear button click', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
});
await flushPromises();
await driver.searchDriver().inputDriver.enterText('foo');
expect(await driver.searchDriver().inputDriver.getValue()).toBe('foo');
await driver.searchDriver().inputDriver.clickClear();
expect(await driver.searchDriver().inputDriver.getValue()).toBe('');
});
it('should postpone the search until the timer if debounce is used', async () => {
jest.useFakeTimers();
const driver = createDriver({
dataSource: paginatedDataSourceWithTimeout,
withSearch: true,
searchDebounceMs: 100,
});
await jest.runOnlyPendingTimers();
await driver.searchDriver().inputDriver.enterText('f');
expect(await driver.mainLoaderDriver().exists()).toBe(false);
jest.advanceTimersByTime(50);
await driver.searchDriver().inputDriver.enterText('fo');
expect(await driver.mainLoaderDriver().exists()).toBe(false);
jest.advanceTimersByTime(50);
await driver.searchDriver().inputDriver.enterText('foo');
expect(await driver.mainLoaderDriver().exists()).toBe(false);
jest.advanceTimersByTime(101);
const doesExist = await driver.mainLoaderDriver().exists();
expect(doesExist).toBe(true);
await jest.runOnlyPendingTimers();
});
it('should not postpone the search if debounce is not used', async () => {
jest.useFakeTimers();
const driver = createDriver({
dataSource: paginatedDataSourceWithTimeout,
});
await jest.runOnlyPendingTimers();
await driver.searchDriver().inputDriver.enterText('f');
expect(await driver.mainLoaderDriver().exists()).toBe(true);
jest.advanceTimersByTime(50);
await jest.runOnlyPendingTimers();
await driver.searchDriver().inputDriver.enterText('fo');
expect(await driver.mainLoaderDriver().exists()).toBe(true);
jest.advanceTimersByTime(50);
await jest.runOnlyPendingTimers();
await driver.searchDriver().inputDriver.enterText('foo');
expect(await driver.mainLoaderDriver().exists()).toBe(true);
await jest.runOnlyPendingTimers();
});
});
describe('pagination', () => {
it(`should render the first 50 items by default, show a small loader when scrolleasync d down,
then render the next page and remove the loader`, async () => {
const dataSource = paginatedDataSourceFactory(
times(55, i => ({ id: i, title: '', subtitle: '' })),
);
const driver = createDriver({ dataSource });
await flushPromises();
expect(await driver.numberOfItemsInList()).toBe(50);
expect(await driver.nextPageLoaderDriver().exists()).toBe(true);
expect(await driver.nextPageLoaderDriver().isSmall()).toBe(true);
await driver.scrollDown();
await flushPromises();
expect(await driver.numberOfItemsInList()).toBe(55);
expect(await driver.nextPageLoaderDriver().exists()).toBe(false);
});
it('should allow configuring items per page', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
itemsPerPage: 2,
});
await flushPromises();
expect(await driver.numberOfItemsInList()).toBe(2);
await driver.scrollDown();
await flushPromises();
expect(await driver.numberOfItemsInList()).toBe(4);
await driver.scrollDown();
await flushPromises();
expect(await driver.numberOfItemsInList()).toBe(6);
});
});
describe('image size', () => {
it('should render tiny images', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
imageSize: 'tiny',
});
await flushPromises();
expect(await driver.getSelectorDriverAt(0).isImageTiny()).toBe(true);
});
it('should render small images', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
imageSize: 'small',
});
await flushPromises();
expect(await driver.getSelectorDriverAt(0).isImageSmall()).toBe(true);
});
it('should render portrait images', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
imageSize: 'portrait',
});
await flushPromises();
expect(await driver.getSelectorDriverAt(0).isImagePortrait()).toBe(
true,
);
});
it('should render large images', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
imageSize: 'large',
});
await flushPromises();
expect(await driver.getSelectorDriverAt(0).isImageLarge()).toBe(true);
});
it('should render cinema images', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
imageSize: 'cinema',
});
await flushPromises();
expect(await driver.getSelectorDriverAt(0).isImageCinema()).toBe(true);
});
it('should render circle images', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
imageShape: 'circle',
});
await flushPromises();
expect(await driver.getSelectorDriverAt(0).isImageCircle()).toBe(true);
});
it('should render rectangular images', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
imageShape: 'rectangular',
});
await flushPromises();
expect(await driver.getSelectorDriverAt(0).isImageRectangular()).toBe(
true,
);
});
});
describe('radio', () => {
const items = [
{ id: 1, title: 'first' },
{ id: '2', title: 'second' },
];
const dataSource = paginatedDataSourceFactory(items);
it('should render radio buttons', async () => {
const driver = createDriver({ dataSource });
await flushPromises();
expect(await driver.getSelectorDriverAt(0).toggleType()).toBe('radio');
expect(await driver.getSelectorDriverAt(1).toggleType()).toBe('radio');
});
it('all rows should be unchecked', async () => {
const driver = createDriver({ dataSource });
await flushPromises();
expect(await driver.getSelectorDriverAt(0).isChecked()).toBe(false);
expect(await driver.getSelectorDriverAt(0).isChecked()).toBe(false);
});
it('should toggle rows when clicking on them', async () => {
const driver = createDriver({ dataSource });
await flushPromises();
await driver.getSelectorDriverAt(0).toggle();
expect(await driver.getSelectorDriverAt(0).isChecked()).toBe(true);
expect(await driver.getSelectorDriverAt(1).isChecked()).toBe(false);
await driver.getSelectorDriverAt(1).toggle();
expect(await driver.getSelectorDriverAt(0).isChecked()).toBe(false);
expect(await driver.getSelectorDriverAt(1).isChecked()).toBe(true);
});
it('should disable the "OK" button until some row is selected', async () => {
const driver = createDriver({ dataSource });
await flushPromises();
expect(await driver.okButtonDriver().isButtonDisabled()).toBe(true);
await driver.getSelectorDriverAt(0).toggle();
expect(await driver.okButtonDriver().isButtonDisabled()).toBe(false);
});
it('should remember the selection if triggered search', async () => {
const driver = createDriver({ dataSource });
await flushPromises();
await driver.getSelectorDriverAt(0).toggle();
await driver.searchDriver().inputDriver.focus();
await driver.searchDriver().inputDriver.enterText('second');
await flushPromises();
expect(await driver.getSelectorDriverAt(0).isChecked()).toBe(false);
await driver.searchDriver().inputDriver.clickClear();
await flushPromises();
expect(await driver.getSelectorDriverAt(0).isChecked()).toBe(true);
});
it('should call "onOk" with the selected item when clicking on "OK" button', async () => {
const stub = jest.fn();
const driver = createDriver({ dataSource, onOk: stub });
await flushPromises();
await driver.getSelectorDriverAt(0).toggle();
await driver.okButtonDriver().click();
expect(stub).toHaveBeenCalledWith(items[0]);
});
});
describe('given `multiple` prop`', () => {
const items = [
{ id: 1, title: 'first' },
{ id: '2', title: 'second' },
];
const dataSource = paginatedDataSourceFactory(items);
const multiselectModalWithItems = async function (_items) {
const _dataSource = paginatedDataSourceFactory(_items);
const driver = createDriver({
dataSource: _dataSource,
multiple: true,
});
await flushPromises();
return driver;
};
it('should render checkboxes', async () => {
const driver = createDriver({ dataSource, multiple: true });
await flushPromises();
expect(await driver.getSelectorDriverAt(0).toggleType()).toBe(
'checkbox',
);
expect(await driver.getSelectorDriverAt(1).toggleType()).toBe(
'checkbox',
);
});
it('should return list when `onOk` is called', async () => {
const spy = jest.fn();
const driver = createDriver({
dataSource,
multiple: true,
onOk: spy,
});
await flushPromises();
await driver.getSelectorDriverAt(0).toggle();
await driver.getSelectorDriverAt(1).toggle();
await driver.okButtonDriver().click();
expect(spy).toHaveBeenCalledWith(items);
});
it('should support a disabled selector', async () => {
const driver = await multiselectModalWithItems([
{ id: 1, title: 'first', disabled: true },
]);
expect(await driver.getSelectorDriverAt(0).isDisabled()).toBe(true);
});
it('should not count selection of disabled items', async () => {
const driver = await multiselectModalWithItems([
{ id: 1, title: 'first', disabled: true },
]);
expect(await driver.footerSelector().getLabel()).toContain('(0)');
});
it('should not count selection of disabled items for deselecting all', async () => {
const driver = await multiselectModalWithItems([
{ id: 1, title: 'first', disabled: true },
]);
await driver.footerSelector().click();
expect(await driver.footerSelector().getLabel()).toContain('(0)');
});
it('should not count selection of disabled items for selecting some', async () => {
const driver = await multiselectModalWithItems([
{ id: 1, title: 'first', disabled: true },
{ id: 2, title: 'sec' },
]);
await driver.getSelectorDriverAt(1).toggle();
expect(await driver.footerSelector().getLabel()).toContain('(1)');
});
it('should count how many left for select all', async () => {
const driver = await multiselectModalWithItems([
{ id: 1, title: 'first', disabled: true },
{ id: 2, title: 'sec' },
]);
expect(await driver.footerSelector().getLabel()).toContain('(1)');
});
});
describe('given items with `selected`', () => {
const items = [
{ id: 1, title: 'first' },
{ id: 2, title: 'second', selected: true },
{ id: 3, title: 'third', disabled: true, selected: true },
];
const dataSource = paginatedDataSourceFactory(items);
it('should show correct label in footer', async () => {
const driver = createDriver({ dataSource, multiple: true });
await flushPromises();
expect(await driver.footerSelector().getLabel()).toContain(
' Deselect All (1)',
);
});
it('should deselect all after click', async () => {
const driver = createDriver({ dataSource, multiple: true });
await flushPromises();
await driver.footerSelector().click();
expect(await driver.footerSelector().getLabel()).toContain(
' Select All (2)',
);
expect(await driver.getSelectorDriverAt(0).isChecked()).toBe(false);
expect(await driver.getSelectorDriverAt(1).isChecked()).toBe(false);
expect(await driver.getSelectorDriverAt(2).isChecked()).toBe(true);
expect(await driver.getSelectorDriverAt(2).isDisabled()).toBe(true);
});
});
describe('defaults', () => {
it('should render empty state', async () => {
const driver = createDriver({
dataSource: emptyDataSource,
});
await flushPromises();
expect(await driver.showsEmptyState()).toBe(true);
expect((await driver.getEmptyState()).textContent).toBe(
"You don't have any items",
);
});
it('should render noResultsFound state', async () => {
const searchValue = 'wubba lubba dub dub';
const driver = createDriver({
dataSource: paginatedDataSource,
});
await flushPromises();
await driver.searchDriver().inputDriver.focus();
await driver.searchDriver().inputDriver.enterText(searchValue);
await flushPromises();
expect(await driver.showsNoResultsFoundState()).toBe(true);
expect((await driver.getNoResultsFoundState()).textContent).toBe(
`No items matched your search "${searchValue}"`,
);
});
it('should render search placeholder "Search..."', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
});
await flushPromises();
expect(await driver.searchDriver().inputDriver.getPlaceholder()).toBe(
'Search...',
);
});
it('should render "OK" button text "Select"', async () => {
const driver = createDriver();
expect(await driver.okButtonDriver().getButtonTextContent()).toBe(
'Select',
);
});
it('should render "Cancel" button text "Cancel"', async () => {
const driver = createDriver();
expect(await driver.cancelButtonDriver().getButtonTextContent()).toBe(
'Cancel',
);
});
it('should render title as "Choose Your Items"', async () => {
const driver = createDriver();
expect(await driver.getTitle()).toBe('Choose Your Items');
});
it('should render large rectangular images', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
});
await flushPromises();
expect(await driver.getSelectorDriverAt(0).isImageLarge()).toBe(true);
expect(await driver.getSelectorDriverAt(0).isImageRectangular()).toBe(
true,
);
});
it('should not render subtitle by default', async () => {
const driver = createDriver({
dataSource: paginatedDataSource,
});
await flushPromises();
expect(await driver.subtitleTextDriver().exists()).toBe(false);
});
});
}
});