mirror of
https://github.com/ansible/awx.git
synced 2026-03-20 10:27:34 -02:30
Add associate modal to nested inventory host list
This commit is contained in:
@@ -5,11 +5,16 @@ class Groups extends Base {
|
|||||||
super(http);
|
super(http);
|
||||||
this.baseUrl = '/api/v2/groups/';
|
this.baseUrl = '/api/v2/groups/';
|
||||||
|
|
||||||
|
this.associateHost = this.associateHost.bind(this);
|
||||||
this.createHost = this.createHost.bind(this);
|
this.createHost = this.createHost.bind(this);
|
||||||
this.readAllHosts = this.readAllHosts.bind(this);
|
this.readAllHosts = this.readAllHosts.bind(this);
|
||||||
this.disassociateHost = this.disassociateHost.bind(this);
|
this.disassociateHost = this.disassociateHost.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
associateHost(id, hostId) {
|
||||||
|
return this.http.post(`${this.baseUrl}${id}/hosts/`, { id: hostId });
|
||||||
|
}
|
||||||
|
|
||||||
createHost(id, data) {
|
createHost(id, data) {
|
||||||
return this.http.post(`${this.baseUrl}${id}/hosts/`, data);
|
return this.http.post(`${this.baseUrl}${id}/hosts/`, data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const ModalList = styled.div`
|
|||||||
|
|
||||||
function OptionsList({
|
function OptionsList({
|
||||||
value,
|
value,
|
||||||
|
contentError,
|
||||||
options,
|
options,
|
||||||
optionCount,
|
optionCount,
|
||||||
searchColumns,
|
searchColumns,
|
||||||
@@ -53,6 +54,7 @@ function OptionsList({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<PaginatedDataList
|
<PaginatedDataList
|
||||||
|
contentError={contentError}
|
||||||
items={options}
|
items={options}
|
||||||
itemCount={optionCount}
|
itemCount={optionCount}
|
||||||
pluralizedItemName={header}
|
pluralizedItemName={header}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import React, { Fragment, useEffect, useCallback } from 'react';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { Button, Modal } from '@patternfly/react-core';
|
||||||
|
import OptionsList from '@components/Lookup/shared/OptionsList';
|
||||||
|
import useRequest from '@util/useRequest';
|
||||||
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
|
import useSelect from '../shared/useSelect';
|
||||||
|
|
||||||
|
const QS_CONFIG = getQSConfig('associate', {
|
||||||
|
page: 1,
|
||||||
|
page_size: 5,
|
||||||
|
order_by: 'name',
|
||||||
|
});
|
||||||
|
|
||||||
|
function AssociateModal({
|
||||||
|
i18n,
|
||||||
|
header = i18n._(t`Items`),
|
||||||
|
title = i18n._(t`Select Items`),
|
||||||
|
onClose,
|
||||||
|
onAssociate,
|
||||||
|
fetchRequest,
|
||||||
|
isModalOpen = false,
|
||||||
|
}) {
|
||||||
|
const history = useHistory();
|
||||||
|
const { selected, handleSelect } = useSelect([]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
request: fetchItems,
|
||||||
|
result: { items, itemCount },
|
||||||
|
error: contentError,
|
||||||
|
isLoading,
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const params = parseQueryString(QS_CONFIG, history.location.search);
|
||||||
|
const {
|
||||||
|
data: { count, results },
|
||||||
|
} = await fetchRequest(params);
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: results,
|
||||||
|
itemCount: count,
|
||||||
|
};
|
||||||
|
}, [fetchRequest, history.location.search]),
|
||||||
|
{
|
||||||
|
items: [],
|
||||||
|
itemCount: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchItems();
|
||||||
|
}, [fetchItems]);
|
||||||
|
|
||||||
|
const clearQSParams = () => {
|
||||||
|
const parts = history.location.search.replace(/^\?/, '').split('&');
|
||||||
|
const ns = QS_CONFIG.namespace;
|
||||||
|
const otherParts = parts.filter(param => !param.startsWith(`${ns}.`));
|
||||||
|
history.replace(`${history.location.pathname}?${otherParts.join('&')}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
await onAssociate(selected);
|
||||||
|
clearQSParams();
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
clearQSParams();
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Modal
|
||||||
|
isFooterLeftAligned
|
||||||
|
isLarge
|
||||||
|
title={title}
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onClose={handleClose}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
aria-label="Save"
|
||||||
|
key="select"
|
||||||
|
variant="primary"
|
||||||
|
onClick={handleSave}
|
||||||
|
isDisabled={selected.length === 0}
|
||||||
|
>
|
||||||
|
{i18n._(t`Save`)}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
aria-label="Cancel"
|
||||||
|
key="cancel"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
{i18n._(t`Cancel`)}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<OptionsList
|
||||||
|
contentError={contentError}
|
||||||
|
deselectItem={item => handleSelect(item)}
|
||||||
|
header={header}
|
||||||
|
isLoading={isLoading}
|
||||||
|
multiple
|
||||||
|
optionCount={itemCount}
|
||||||
|
options={items}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
readOnly={false}
|
||||||
|
selectItem={item => handleSelect(item)}
|
||||||
|
value={selected}
|
||||||
|
searchColumns={[
|
||||||
|
{
|
||||||
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name',
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Created By (Username)`),
|
||||||
|
key: 'created_by__username',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Modified By (Username)`),
|
||||||
|
key: 'modified_by__username',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
sortColumns={[
|
||||||
|
{
|
||||||
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n()(AssociateModal);
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
|
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||||
|
import AssociateModal from './AssociateModal';
|
||||||
|
import mockHosts from '../shared/data.hosts.json';
|
||||||
|
|
||||||
|
jest.mock('@api');
|
||||||
|
|
||||||
|
describe('<AssociateModal />', () => {
|
||||||
|
let wrapper;
|
||||||
|
const onClose = jest.fn();
|
||||||
|
const onAssociate = jest.fn().mockResolvedValue();
|
||||||
|
const fetchRequest = jest.fn().mockReturnValue({ data: { ...mockHosts } });
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<AssociateModal
|
||||||
|
onClose={onClose}
|
||||||
|
onAssociate={onAssociate}
|
||||||
|
fetchRequest={fetchRequest}
|
||||||
|
isModalOpen
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.unmount();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render successfully', () => {
|
||||||
|
expect(wrapper.find('AssociateModal').length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fetch and render list items', () => {
|
||||||
|
expect(fetchRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(wrapper.find('CheckboxListItem').length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should update selected list chips when items are selected', () => {
|
||||||
|
expect(wrapper.find('SelectedList Chip')).toHaveLength(0);
|
||||||
|
act(() => {
|
||||||
|
wrapper
|
||||||
|
.find('CheckboxListItem')
|
||||||
|
.first()
|
||||||
|
.invoke('onSelect')();
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('SelectedList Chip')).toHaveLength(1);
|
||||||
|
wrapper.find('SelectedList Chip button').simulate('click');
|
||||||
|
expect(wrapper.find('SelectedList Chip')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('save button should call onAssociate', () => {
|
||||||
|
act(() => {
|
||||||
|
wrapper
|
||||||
|
.find('CheckboxListItem')
|
||||||
|
.first()
|
||||||
|
.invoke('onSelect')();
|
||||||
|
});
|
||||||
|
wrapper.find('button[aria-label="Save"]').simulate('click');
|
||||||
|
expect(onAssociate).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cancel button should call onClose', () => {
|
||||||
|
wrapper.find('button[aria-label="Cancel"]').simulate('click');
|
||||||
|
expect(onClose).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,17 +2,22 @@ import React, { useEffect, useCallback, useState } from 'react';
|
|||||||
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
import { getQSConfig, mergeParams, parseQueryString } from '@util/qs';
|
||||||
import { GroupsAPI, InventoriesAPI } from '@api';
|
import { GroupsAPI, InventoriesAPI } from '@api';
|
||||||
|
|
||||||
|
import useRequest, {
|
||||||
|
useDeleteItems,
|
||||||
|
useDismissableError,
|
||||||
|
} from '@util/useRequest';
|
||||||
import AlertModal from '@components/AlertModal';
|
import AlertModal from '@components/AlertModal';
|
||||||
import DataListToolbar from '@components/DataListToolbar';
|
import DataListToolbar from '@components/DataListToolbar';
|
||||||
import ErrorDetail from '@components/ErrorDetail';
|
import ErrorDetail from '@components/ErrorDetail';
|
||||||
import PaginatedDataList from '@components/PaginatedDataList';
|
import PaginatedDataList from '@components/PaginatedDataList';
|
||||||
import useRequest, { useDeleteItems } from '@util/useRequest';
|
|
||||||
import InventoryGroupHostListItem from './InventoryGroupHostListItem';
|
import InventoryGroupHostListItem from './InventoryGroupHostListItem';
|
||||||
|
import AssociateModal from './AssociateModal';
|
||||||
import AddHostDropdown from './AddHostDropdown';
|
import AddHostDropdown from './AddHostDropdown';
|
||||||
import DisassociateButton from './DisassociateButton';
|
import DisassociateButton from './DisassociateButton';
|
||||||
|
import useSelect from '../shared/useSelect';
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('host', {
|
const QS_CONFIG = getQSConfig('host', {
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -21,7 +26,6 @@ const QS_CONFIG = getQSConfig('host', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function InventoryGroupHostList({ i18n }) {
|
function InventoryGroupHostList({ i18n }) {
|
||||||
const [selected, setSelected] = useState([]);
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const { id: inventoryId, groupId } = useParams();
|
const { id: inventoryId, groupId } = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -52,29 +56,18 @@ function InventoryGroupHostList({ i18n }) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { selected, isAllSelected, handleSelect, setSelected } = useSelect(
|
||||||
|
hosts
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchHosts();
|
fetchHosts();
|
||||||
}, [fetchHosts]);
|
}, [fetchHosts]);
|
||||||
|
|
||||||
const handleSelectAll = isSelected => {
|
|
||||||
setSelected(isSelected ? [...hosts] : []);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelect = row => {
|
|
||||||
if (selected.some(s => s.id === row.id)) {
|
|
||||||
setSelected(selected.filter(s => s.id !== row.id));
|
|
||||||
} else {
|
|
||||||
setSelected(selected.concat(row));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAllSelected = selected.length > 0 && selected.length === hosts.length;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isLoading: isDisassociateLoading,
|
isLoading: isDisassociateLoading,
|
||||||
deleteItems: disassociateHosts,
|
deleteItems: disassociateHosts,
|
||||||
deletionError: disassociateError,
|
deletionError: disassociateError,
|
||||||
clearDeletionError: clearDisassociateError,
|
|
||||||
} = useDeleteItems(
|
} = useDeleteItems(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
@@ -93,6 +86,34 @@ function InventoryGroupHostList({ i18n }) {
|
|||||||
setSelected([]);
|
setSelected([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchHostsToAssociate = useCallback(
|
||||||
|
params => {
|
||||||
|
return InventoriesAPI.readHosts(
|
||||||
|
inventoryId,
|
||||||
|
mergeParams(params, { not__groups: groupId })
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[groupId, inventoryId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { request: handleAssociate, error: associateError } = useRequest(
|
||||||
|
useCallback(
|
||||||
|
async hostsToAssociate => {
|
||||||
|
await Promise.all(
|
||||||
|
hostsToAssociate.map(host =>
|
||||||
|
GroupsAPI.associateHost(groupId, host.id)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
fetchHosts();
|
||||||
|
},
|
||||||
|
[groupId, fetchHosts]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const { error, dismissError } = useDismissableError(
|
||||||
|
associateError || disassociateError
|
||||||
|
);
|
||||||
|
|
||||||
const canAdd =
|
const canAdd =
|
||||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||||
const addFormUrl = `/inventories/inventory/${inventoryId}/groups/${groupId}/nested_hosts/add`;
|
const addFormUrl = `/inventories/inventory/${inventoryId}/groups/${groupId}/nested_hosts/add`;
|
||||||
@@ -133,7 +154,9 @@ function InventoryGroupHostList({ i18n }) {
|
|||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
showSelectAll
|
||||||
isAllSelected={isAllSelected}
|
isAllSelected={isAllSelected}
|
||||||
onSelectAll={handleSelectAll}
|
onSelectAll={isSelected =>
|
||||||
|
setSelected(isSelected ? [...hosts] : [])
|
||||||
|
}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
additionalControls={[
|
additionalControls={[
|
||||||
...(canAdd
|
...(canAdd
|
||||||
@@ -179,25 +202,26 @@ function InventoryGroupHostList({ i18n }) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
<AlertModal
|
<AssociateModal
|
||||||
isOpen={isModalOpen}
|
header={i18n._(t`Hosts`)}
|
||||||
variant="info"
|
fetchRequest={fetchHostsToAssociate}
|
||||||
title={i18n._(t`Select Hosts`)}
|
isModalOpen={isModalOpen}
|
||||||
|
onAssociate={handleAssociate}
|
||||||
onClose={() => setIsModalOpen(false)}
|
onClose={() => setIsModalOpen(false)}
|
||||||
>
|
title={i18n._(t`Select Hosts`)}
|
||||||
{/* ADD/ASSOCIATE HOST MODAL PLACEHOLDER */}
|
/>
|
||||||
{i18n._(t`Host Select Modal`)}
|
|
||||||
</AlertModal>
|
|
||||||
)}
|
)}
|
||||||
{disassociateError && (
|
{(associateError || disassociateError) && (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
isOpen={disassociateError}
|
isOpen={error}
|
||||||
variant="error"
|
onClose={dismissError}
|
||||||
title={i18n._(t`Error!`)}
|
title={i18n._(t`Error!`)}
|
||||||
onClose={clearDisassociateError}
|
variant="error"
|
||||||
>
|
>
|
||||||
{i18n._(t`Failed to disassociate one or more hosts.`)}
|
{associateError
|
||||||
<ErrorDetail error={disassociateError} />
|
? i18n._(t`Failed to associate.`)
|
||||||
|
: i18n._(t`Failed to disassociate one or more hosts.`)}
|
||||||
|
<ErrorDetail error={error} />
|
||||||
</AlertModal>
|
</AlertModal>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -155,9 +155,41 @@ describe('<InventoryGroupHostList />', () => {
|
|||||||
wrapper
|
wrapper
|
||||||
.find('DropdownItem[aria-label="add existing host"]')
|
.find('DropdownItem[aria-label="add existing host"]')
|
||||||
.simulate('click');
|
.simulate('click');
|
||||||
expect(wrapper.find('AlertModal').length).toBe(1);
|
expect(wrapper.find('AssociateModal').length).toBe(1);
|
||||||
wrapper.find('ModalBoxCloseButton').simulate('click');
|
wrapper.find('ModalBoxCloseButton').simulate('click');
|
||||||
expect(wrapper.find('AlertModal').length).toBe(0);
|
expect(wrapper.find('AssociateModal').length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should make expected api request when associating hosts', async () => {
|
||||||
|
GroupsAPI.associateHost.mockResolvedValue();
|
||||||
|
InventoriesAPI.readHosts.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
count: 1,
|
||||||
|
results: [{ id: 123, name: 'foo', url: '/api/v2/hosts/123/' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
wrapper
|
||||||
|
.find('DropdownToggle button[aria-label="add host"]')
|
||||||
|
.simulate('click');
|
||||||
|
await act(async () => {
|
||||||
|
wrapper
|
||||||
|
.find('DropdownItem[aria-label="add existing host"]')
|
||||||
|
.simulate('click');
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper
|
||||||
|
.find('CheckboxListItem')
|
||||||
|
.first()
|
||||||
|
.invoke('onSelect')();
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('button[aria-label="Save"]').simulate('click');
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'AssociateModal', el => el.length === 0);
|
||||||
|
expect(InventoriesAPI.readHosts).toHaveBeenCalledTimes(1);
|
||||||
|
expect(GroupsAPI.associateHost).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should navigate to host add form when adding a new host', async () => {
|
test('should navigate to host add form when adding a new host', async () => {
|
||||||
|
|||||||
27
awx/ui_next/src/screens/Inventory/shared/useSelect.jsx
Normal file
27
awx/ui_next/src/screens/Inventory/shared/useSelect.jsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* useSelect hook provides a way to read and update a selected list
|
||||||
|
* Param: array of list items
|
||||||
|
* Returns: {
|
||||||
|
* selected: array of selected list items
|
||||||
|
* isAllSelected: boolean that indicates if all items are selected
|
||||||
|
* handleSelect: function that adds and removes items from selected list
|
||||||
|
* setSelected: setter function
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function useSelect(list = []) {
|
||||||
|
const [selected, setSelected] = useState([]);
|
||||||
|
const isAllSelected = selected.length > 0 && selected.length === list.length;
|
||||||
|
|
||||||
|
const handleSelect = row => {
|
||||||
|
if (selected.some(s => s.id === row.id)) {
|
||||||
|
setSelected(prevState => [...prevState.filter(i => i.id !== row.id)]);
|
||||||
|
} else {
|
||||||
|
setSelected(prevState => [...prevState, row]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { selected, isAllSelected, handleSelect, setSelected };
|
||||||
|
}
|
||||||
75
awx/ui_next/src/screens/Inventory/shared/useSelect.test.jsx
Normal file
75
awx/ui_next/src/screens/Inventory/shared/useSelect.test.jsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import useSelect from './useSelect';
|
||||||
|
|
||||||
|
const array = [{ id: '1' }, { id: '2' }, { id: '3' }];
|
||||||
|
|
||||||
|
const TestHook = ({ callback }) => {
|
||||||
|
callback();
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const testHook = callback => {
|
||||||
|
mount(<TestHook callback={callback} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('useSelect hook', () => {
|
||||||
|
let selected;
|
||||||
|
let isAllSelected;
|
||||||
|
let handleSelect;
|
||||||
|
let setSelected;
|
||||||
|
|
||||||
|
test('should return expected initial values', () => {
|
||||||
|
testHook(() => {
|
||||||
|
({ selected, isAllSelected, handleSelect, setSelected } = useSelect());
|
||||||
|
});
|
||||||
|
expect(selected).toEqual([]);
|
||||||
|
expect(isAllSelected).toEqual(false);
|
||||||
|
expect(handleSelect).toBeInstanceOf(Function);
|
||||||
|
expect(setSelected).toBeInstanceOf(Function);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleSelect should update and filter selected items', () => {
|
||||||
|
testHook(() => {
|
||||||
|
({ selected, isAllSelected, handleSelect, setSelected } = useSelect());
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
handleSelect(array[0]);
|
||||||
|
});
|
||||||
|
expect(selected).toEqual([array[0]]);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
handleSelect(array[0]);
|
||||||
|
});
|
||||||
|
expect(selected).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return expected isAllSelected value', () => {
|
||||||
|
testHook(() => {
|
||||||
|
({ selected, isAllSelected, handleSelect, setSelected } = useSelect(
|
||||||
|
array
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
handleSelect(array[0]);
|
||||||
|
});
|
||||||
|
expect(selected).toEqual([array[0]]);
|
||||||
|
expect(isAllSelected).toEqual(false);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
handleSelect(array[1]);
|
||||||
|
handleSelect(array[2]);
|
||||||
|
});
|
||||||
|
expect(selected).toEqual(array);
|
||||||
|
expect(isAllSelected).toEqual(true);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
setSelected([]);
|
||||||
|
});
|
||||||
|
expect(selected).toEqual([]);
|
||||||
|
expect(isAllSelected).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user