mirror of
https://github.com/ansible/awx.git
synced 2026-05-13 12:27:37 -02:30
Merge pull request #5288 from AlexSCorey/5270-InventoryAddLinks
Adds AddDropDownButton removes TemplateAddButton Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -6,4 +6,6 @@ export const asyncFlush = () => new Promise((resolve) => setImmediate(resolve));
|
|||||||
const enzyme = require('enzyme');
|
const enzyme = require('enzyme');
|
||||||
const Adapter = require('enzyme-adapter-react-16');
|
const Adapter = require('enzyme-adapter-react-16');
|
||||||
|
|
||||||
|
jest.setTimeout(5000 * 4);
|
||||||
|
|
||||||
enzyme.configure({ adapter: new Adapter() });
|
enzyme.configure({ adapter: new Adapter() });
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { Link, withRouter } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import PropTypes from 'prop-types';
|
||||||
import { t } from '@lingui/macro';
|
|
||||||
import { Dropdown, DropdownPosition } from '@patternfly/react-core';
|
import { Dropdown, DropdownPosition } from '@patternfly/react-core';
|
||||||
import { ToolbarAddButton } from '@components/PaginatedDataList';
|
import { ToolbarAddButton } from '@components/PaginatedDataList';
|
||||||
|
|
||||||
function TemplateAddButton({ match, i18n }) {
|
function AddDropDownButton({ dropdownItems }) {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const element = useRef(null);
|
const element = useRef(null);
|
||||||
|
|
||||||
@@ -29,26 +28,28 @@ function TemplateAddButton({ match, i18n }) {
|
|||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
position={DropdownPosition.right}
|
position={DropdownPosition.right}
|
||||||
toggle={<ToolbarAddButton onClick={() => setIsOpen(!isOpen)} />}
|
toggle={<ToolbarAddButton onClick={() => setIsOpen(!isOpen)} />}
|
||||||
dropdownItems={[
|
dropdownItems={dropdownItems.map(item => (
|
||||||
<Link
|
<Link
|
||||||
key="job"
|
|
||||||
className="pf-c-dropdown__menu-item"
|
className="pf-c-dropdown__menu-item"
|
||||||
to={`${match.url}/job_template/add/`}
|
key={item.url}
|
||||||
|
to={item.url}
|
||||||
>
|
>
|
||||||
{i18n._(t`Job Template`)}
|
{item.label}
|
||||||
</Link>,
|
</Link>
|
||||||
<Link
|
))}
|
||||||
key="workflow"
|
|
||||||
className="pf-c-dropdown__menu-item"
|
|
||||||
to={`${match.url}_workflow/add/`}
|
|
||||||
>
|
|
||||||
{i18n._(t`Workflow Template`)}
|
|
||||||
</Link>,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { TemplateAddButton as _TemplateAddButton };
|
AddDropDownButton.propTypes = {
|
||||||
export default withI18n()(withRouter(TemplateAddButton));
|
dropdownItems: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
url: PropTypes.string.isRequired,
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { AddDropDownButton as _AddDropDownButton };
|
||||||
|
export default AddDropDownButton;
|
||||||
@@ -1,22 +1,35 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
import TemplateAddButton from './TemplateAddButton';
|
import AddDropDownButton from './AddDropDownButton';
|
||||||
|
|
||||||
describe('<TemplateAddButton />', () => {
|
describe('<AddDropDownButton />', () => {
|
||||||
|
const dropdownItems = [
|
||||||
|
{
|
||||||
|
key: 'inventory',
|
||||||
|
label: 'Inventory',
|
||||||
|
url: `inventory/inventory/add/`,
|
||||||
|
},
|
||||||
|
];
|
||||||
test('should be closed initially', () => {
|
test('should be closed initially', () => {
|
||||||
const wrapper = mountWithContexts(<TemplateAddButton />);
|
const wrapper = mountWithContexts(
|
||||||
|
<AddDropDownButton dropdownItems={dropdownItems} />
|
||||||
|
);
|
||||||
expect(wrapper.find('Dropdown').prop('isOpen')).toEqual(false);
|
expect(wrapper.find('Dropdown').prop('isOpen')).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should render two links', () => {
|
test('should render two links', () => {
|
||||||
const wrapper = mountWithContexts(<TemplateAddButton />);
|
const wrapper = mountWithContexts(
|
||||||
|
<AddDropDownButton dropdownItems={dropdownItems} />
|
||||||
|
);
|
||||||
wrapper.find('button').simulate('click');
|
wrapper.find('button').simulate('click');
|
||||||
expect(wrapper.find('Dropdown').prop('isOpen')).toEqual(true);
|
expect(wrapper.find('Dropdown').prop('isOpen')).toEqual(true);
|
||||||
expect(wrapper.find('Link')).toHaveLength(2);
|
expect(wrapper.find('Link')).toHaveLength(dropdownItems.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should close when button re-clicked', () => {
|
test('should close when button re-clicked', () => {
|
||||||
const wrapper = mountWithContexts(<TemplateAddButton />);
|
const wrapper = mountWithContexts(
|
||||||
|
<AddDropDownButton dropdownItems={dropdownItems} />
|
||||||
|
);
|
||||||
wrapper.find('button').simulate('click');
|
wrapper.find('button').simulate('click');
|
||||||
expect(wrapper.find('Dropdown').prop('isOpen')).toEqual(true);
|
expect(wrapper.find('Dropdown').prop('isOpen')).toEqual(true);
|
||||||
wrapper.find('button').simulate('click');
|
wrapper.find('button').simulate('click');
|
||||||
1
awx/ui_next/src/components/AddDropDownButton/index.js
Normal file
1
awx/ui_next/src/components/AddDropDownButton/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './AddDropDownButton';
|
||||||
@@ -1,15 +1,9 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { withRouter, Link } from 'react-router-dom';
|
import { withRouter } 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 {
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
Card,
|
|
||||||
PageSection,
|
|
||||||
Dropdown,
|
|
||||||
DropdownItem,
|
|
||||||
DropdownPosition,
|
|
||||||
} from '@patternfly/react-core';
|
|
||||||
|
|
||||||
import { InventoriesAPI } from '@api';
|
import { InventoriesAPI } from '@api';
|
||||||
import AlertModal from '@components/AlertModal';
|
import AlertModal from '@components/AlertModal';
|
||||||
@@ -17,10 +11,10 @@ import DatalistToolbar from '@components/DataListToolbar';
|
|||||||
import ErrorDetail from '@components/ErrorDetail';
|
import ErrorDetail from '@components/ErrorDetail';
|
||||||
import PaginatedDataList, {
|
import PaginatedDataList, {
|
||||||
ToolbarDeleteButton,
|
ToolbarDeleteButton,
|
||||||
ToolbarAddButton,
|
|
||||||
} from '@components/PaginatedDataList';
|
} from '@components/PaginatedDataList';
|
||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
|
||||||
|
|
||||||
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
|
import AddDropDownButton from '@components/AddDropDownButton';
|
||||||
import InventoryListItem from './InventoryListItem';
|
import InventoryListItem from './InventoryListItem';
|
||||||
|
|
||||||
// The type value in const QS_CONFIG below does not have a space between job_inventory and
|
// The type value in const QS_CONFIG below does not have a space between job_inventory and
|
||||||
@@ -42,7 +36,6 @@ class InventoriesList extends Component {
|
|||||||
selected: [],
|
selected: [],
|
||||||
inventories: [],
|
inventories: [],
|
||||||
itemCount: 0,
|
itemCount: 0,
|
||||||
isAddOpen: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.loadInventories = this.loadInventories.bind(this);
|
this.loadInventories = this.loadInventories.bind(this);
|
||||||
@@ -50,7 +43,6 @@ class InventoriesList extends Component {
|
|||||||
this.handleSelect = this.handleSelect.bind(this);
|
this.handleSelect = this.handleSelect.bind(this);
|
||||||
this.handleInventoryDelete = this.handleInventoryDelete.bind(this);
|
this.handleInventoryDelete = this.handleInventoryDelete.bind(this);
|
||||||
this.handleDeleteErrorClose = this.handleDeleteErrorClose.bind(this);
|
this.handleDeleteErrorClose = this.handleDeleteErrorClose.bind(this);
|
||||||
this.handleAddToggle = this.handleAddToggle.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -65,10 +57,6 @@ class InventoriesList extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
document.removeEventListener('click', this.handleAddToggle, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeleteErrorClose() {
|
handleDeleteErrorClose() {
|
||||||
this.setState({ deletionError: null });
|
this.setState({ deletionError: null });
|
||||||
}
|
}
|
||||||
@@ -88,21 +76,6 @@ class InventoriesList extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddToggle(e) {
|
|
||||||
const { isAddOpen } = this.state;
|
|
||||||
document.addEventListener('click', this.handleAddToggle, false);
|
|
||||||
|
|
||||||
if (this.node && this.node.contains(e.target) && isAddOpen) {
|
|
||||||
document.removeEventListener('click', this.handleAddToggle, false);
|
|
||||||
this.setState({ isAddOpen: false });
|
|
||||||
} else if (this.node && this.node.contains(e.target) && !isAddOpen) {
|
|
||||||
this.setState({ isAddOpen: true });
|
|
||||||
} else {
|
|
||||||
this.setState({ isAddOpen: false });
|
|
||||||
document.removeEventListener('click', this.handleAddToggle, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleInventoryDelete() {
|
async handleInventoryDelete() {
|
||||||
const { selected, itemCount } = this.state;
|
const { selected, itemCount } = this.state;
|
||||||
|
|
||||||
@@ -168,14 +141,27 @@ class InventoriesList extends Component {
|
|||||||
inventories,
|
inventories,
|
||||||
itemCount,
|
itemCount,
|
||||||
selected,
|
selected,
|
||||||
isAddOpen,
|
|
||||||
actions,
|
actions,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { match, i18n } = this.props;
|
const { match, i18n } = this.props;
|
||||||
const canAdd =
|
const canAdd =
|
||||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||||
const isAllSelected =
|
const isAllSelected = selected.length === inventories.length;
|
||||||
selected.length > 0 && selected.length === inventories.length;
|
const addButton = (
|
||||||
|
<AddDropDownButton
|
||||||
|
key="add"
|
||||||
|
dropdownItems={[
|
||||||
|
{
|
||||||
|
label: i18n._(t`Inventory`),
|
||||||
|
url: `${match.url}/inventory/add/`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n._(t`Smart Inventory`),
|
||||||
|
url: `${match.url}/smart_inventory/add/`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
@@ -221,35 +207,7 @@ class InventoriesList extends Component {
|
|||||||
itemsToDelete={selected}
|
itemsToDelete={selected}
|
||||||
pluralizedItemName="Inventories"
|
pluralizedItemName="Inventories"
|
||||||
/>,
|
/>,
|
||||||
canAdd && (
|
canAdd && addButton,
|
||||||
<div
|
|
||||||
ref={node => {
|
|
||||||
this.node = node;
|
|
||||||
}}
|
|
||||||
key="add"
|
|
||||||
>
|
|
||||||
<Dropdown
|
|
||||||
isPlain
|
|
||||||
isOpen={isAddOpen}
|
|
||||||
position={DropdownPosition.right}
|
|
||||||
toggle={
|
|
||||||
<ToolbarAddButton onClick={this.handleAddToggle} />
|
|
||||||
}
|
|
||||||
dropdownItems={[
|
|
||||||
<DropdownItem key="inventory">
|
|
||||||
<Link to={`${match.url}/inventory/add/`}>
|
|
||||||
{i18n._(t`Inventory`)}
|
|
||||||
</Link>
|
|
||||||
</DropdownItem>,
|
|
||||||
<DropdownItem key="smart_inventory">
|
|
||||||
<Link to={`${match.url}/smart_inventory/add/`}>
|
|
||||||
{i18n._(t`Smart Inventory`)}
|
|
||||||
</Link>
|
|
||||||
</DropdownItem>,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -267,35 +225,7 @@ class InventoriesList extends Component {
|
|||||||
isSelected={selected.some(row => row.id === inventory.id)}
|
isSelected={selected.some(row => row.id === inventory.id)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
emptyStateControls={
|
emptyStateControls={canAdd && addButton}
|
||||||
canAdd && (
|
|
||||||
<div
|
|
||||||
ref={node => {
|
|
||||||
this.node = node;
|
|
||||||
}}
|
|
||||||
key="add"
|
|
||||||
>
|
|
||||||
<Dropdown
|
|
||||||
isPlain
|
|
||||||
isOpen={isAddOpen}
|
|
||||||
position={DropdownPosition.right}
|
|
||||||
toggle={<ToolbarAddButton onClick={this.handleAddToggle} />}
|
|
||||||
dropdownItems={[
|
|
||||||
<DropdownItem key="inventory">
|
|
||||||
<Link to={`${match.url}/inventory/add/`}>
|
|
||||||
{i18n._(t`Inventory`)}
|
|
||||||
</Link>
|
|
||||||
</DropdownItem>,
|
|
||||||
<DropdownItem key="smart_inventory">
|
|
||||||
<Link to={`${match.url}/smart_inventory/add/`}>
|
|
||||||
{i18n._(t`Smart Inventory`)}
|
|
||||||
</Link>
|
|
||||||
</DropdownItem>,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<AlertModal
|
<AlertModal
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ describe('<InventoriesList />', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Add button shown for users without ability to POST', async done => {
|
test('Add button shown for users with ability to POST', async done => {
|
||||||
const wrapper = mountWithContexts(<InventoriesList />);
|
const wrapper = mountWithContexts(<InventoriesList />);
|
||||||
await waitForElement(
|
await waitForElement(
|
||||||
wrapper,
|
wrapper,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ describe('<ProjectForm />', () => {
|
|||||||
id: 100,
|
id: 100,
|
||||||
credential_type_id: 4,
|
credential_type_id: 4,
|
||||||
kind: 'scm',
|
kind: 'scm',
|
||||||
name: 'alpha',
|
name: 'Foo',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -200,17 +200,22 @@ describe('<JobTemplateEdit />', () => {
|
|||||||
data: { ...updatedTemplateData },
|
data: { ...updatedTemplateData },
|
||||||
});
|
});
|
||||||
const formik = wrapper.find('Formik').instance();
|
const formik = wrapper.find('Formik').instance();
|
||||||
const changeState = new Promise(resolve => {
|
const changeState = await act(
|
||||||
const values = {
|
() =>
|
||||||
...mockJobTemplate,
|
new Promise(resolve => {
|
||||||
...updatedTemplateData,
|
const values = {
|
||||||
labels,
|
...mockJobTemplate,
|
||||||
instanceGroups: [],
|
...updatedTemplateData,
|
||||||
};
|
labels,
|
||||||
formik.setState({ values }, () => resolve());
|
instanceGroups: [],
|
||||||
});
|
};
|
||||||
|
formik.setState({ values }, () => resolve());
|
||||||
|
})
|
||||||
|
);
|
||||||
await changeState;
|
await changeState;
|
||||||
wrapper.find('button[aria-label="Save"]').simulate('click');
|
await act(async () => {
|
||||||
|
wrapper.find('button[aria-label="Save"]').simulate('click');
|
||||||
|
});
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
|
|
||||||
expect(JobTemplatesAPI.update).toHaveBeenCalledWith(1, {
|
expect(JobTemplatesAPI.update).toHaveBeenCalledWith(1, {
|
||||||
@@ -236,7 +241,9 @@ describe('<JobTemplateEdit />', () => {
|
|||||||
'button[aria-label="Cancel"]',
|
'button[aria-label="Cancel"]',
|
||||||
e => e.length === 1
|
e => e.length === 1
|
||||||
);
|
);
|
||||||
cancelButton.prop('onClick')();
|
await act(async () => {
|
||||||
|
cancelButton.prop('onClick')();
|
||||||
|
});
|
||||||
expect(history.location.pathname).toEqual(
|
expect(history.location.pathname).toEqual(
|
||||||
'/templates/job_template/1/details'
|
'/templates/job_template/1/details'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import PaginatedDataList, {
|
|||||||
} from '@components/PaginatedDataList';
|
} from '@components/PaginatedDataList';
|
||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
|
|
||||||
|
import AddDropDownButton from '@components/AddDropDownButton';
|
||||||
import TemplateListItem from './TemplateListItem';
|
import TemplateListItem from './TemplateListItem';
|
||||||
import TemplateAddButton from './TemplateAddButton';
|
|
||||||
|
|
||||||
// The type value in const QS_CONFIG below does not have a space between job_template and
|
// The type value in const QS_CONFIG below does not have a space between job_template and
|
||||||
// workflow_job_template so the params sent to the API match what the api expects.
|
// workflow_job_template so the params sent to the API match what the api expects.
|
||||||
@@ -161,6 +161,21 @@ class TemplatesList extends Component {
|
|||||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||||
const isAllSelected =
|
const isAllSelected =
|
||||||
selected.length === templates.length && selected.length > 0;
|
selected.length === templates.length && selected.length > 0;
|
||||||
|
const addButton = (
|
||||||
|
<AddDropDownButton
|
||||||
|
key="add"
|
||||||
|
dropdownItems={[
|
||||||
|
{
|
||||||
|
label: i18n._(t`Template`),
|
||||||
|
url: `${match.url}/template/add/`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n._(t`Workflow Template`),
|
||||||
|
url: `${match.url}/_workflow/add/`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
@@ -206,7 +221,7 @@ class TemplatesList extends Component {
|
|||||||
itemsToDelete={selected}
|
itemsToDelete={selected}
|
||||||
pluralizedItemName="Templates"
|
pluralizedItemName="Templates"
|
||||||
/>,
|
/>,
|
||||||
canAdd && <TemplateAddButton key="add" />,
|
canAdd && addButton,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -220,7 +235,7 @@ class TemplatesList extends Component {
|
|||||||
isSelected={selected.some(row => row.id === template.id)}
|
isSelected={selected.some(row => row.id === template.id)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
emptyStateControls={canAdd && <TemplateAddButton />}
|
emptyStateControls={canAdd && addButton}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<AlertModal
|
<AlertModal
|
||||||
|
|||||||
@@ -157,10 +157,12 @@ describe('<JobTemplateForm />', () => {
|
|||||||
target: { value: 'new baz type', name: 'playbook' },
|
target: { value: 'new baz type', name: 'playbook' },
|
||||||
});
|
});
|
||||||
expect(form.state('values').playbook).toEqual('new baz type');
|
expect(form.state('values').playbook).toEqual('new baz type');
|
||||||
wrapper
|
await act(async () => {
|
||||||
.find('CredentialChip')
|
wrapper
|
||||||
.at(0)
|
.find('CredentialChip')
|
||||||
.prop('onClick')();
|
.at(0)
|
||||||
|
.prop('onClick')();
|
||||||
|
});
|
||||||
expect(form.state('values').credentials).toEqual([
|
expect(form.state('values').credentials).toEqual([
|
||||||
{ id: 2, kind: 'ssh', name: 'Bar' },
|
{ id: 2, kind: 'ssh', name: 'Bar' },
|
||||||
]);
|
]);
|
||||||
@@ -180,7 +182,9 @@ describe('<JobTemplateForm />', () => {
|
|||||||
});
|
});
|
||||||
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
|
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
|
||||||
expect(handleSubmit).not.toHaveBeenCalled();
|
expect(handleSubmit).not.toHaveBeenCalled();
|
||||||
wrapper.find('button[aria-label="Save"]').simulate('click');
|
await act(async () => {
|
||||||
|
wrapper.find('button[aria-label="Save"]').simulate('click');
|
||||||
|
});
|
||||||
await sleep(1);
|
await sleep(1);
|
||||||
expect(handleSubmit).toBeCalled();
|
expect(handleSubmit).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user