diff --git a/awx/ui_next/src/components/Lookup/Lookup.jsx b/awx/ui_next/src/components/Lookup/Lookup.jsx
index afb67b54c4..b431ce4ac2 100644
--- a/awx/ui_next/src/components/Lookup/Lookup.jsx
+++ b/awx/ui_next/src/components/Lookup/Lookup.jsx
@@ -30,6 +30,7 @@ const SearchButton = styled(Button)`
var(--pf-global--BorderColor--200);
}
`;
+SearchButton.displayName = 'SearchButton';
const InputGroup = styled(PFInputGroup)`
${props =>
@@ -120,7 +121,7 @@ function Lookup(props) {
-
+
{(multiple ? value : [value]).map(item =>
renderItemChip({
item,
diff --git a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx
index 02764f3de3..2d1eff8965 100644
--- a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx
+++ b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx
@@ -32,7 +32,7 @@ async function loadCredentials(params, selectedCredentialTypeId) {
}
function MultiCredentialsLookup(props) {
- const { history, tooltip, value, onChange, onError, i18n } = props;
+ const { tooltip, value, onChange, onError, history, i18n } = props;
const [credentialTypes, setCredentialTypes] = useState([]);
const [selectedType, setSelectedType] = useState(null);
const [credentials, setCredentials] = useState([]);
@@ -50,7 +50,7 @@ function MultiCredentialsLookup(props) {
onError(err);
}
})();
- }, []);
+ }, [onError]);
useEffect(() => {
(async () => {
@@ -69,7 +69,7 @@ function MultiCredentialsLookup(props) {
onError(err);
}
})();
- }, [selectedType]);
+ }, [selectedType, history.location.search, onError]);
const isMultiple = selectedType && selectedType.name === 'Vault';
const renderChip = ({ item, removeItem, canDelete }) => (
diff --git a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.test.jsx b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.test.jsx
index cc00396525..baaea96776 100644
--- a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.test.jsx
+++ b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.test.jsx
@@ -1,6 +1,7 @@
import React from 'react';
-
-import { mountWithContexts } from '@testUtils/enzymeHelpers';
+import { act } from 'react-dom/test-utils';
+import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
+import { sleep } from '@testUtils/testUtils';
import MultiCredentialsLookup from './MultiCredentialsLookup';
import { CredentialsAPI, CredentialTypesAPI } from '@api';
@@ -8,9 +9,6 @@ jest.mock('@api');
describe('', () => {
let wrapper;
- let lookup;
- let credLookup;
- let onChange;
const credentials = [
{ id: 1, kind: 'cloud', name: 'Foo', url: 'www.google.com' },
@@ -18,8 +16,9 @@ describe('', () => {
{ name: 'Gatsby', id: 21, kind: 'vault' },
{ name: 'Gatsby', id: 8, kind: 'Machine' },
];
+
beforeEach(() => {
- CredentialTypesAPI.read.mockResolvedValue({
+ CredentialTypesAPI.read.mockResolvedValueOnce({
data: {
results: [
{
@@ -46,17 +45,6 @@ describe('', () => {
count: 3,
},
});
- onChange = jest.fn();
- wrapper = mountWithContexts(
- {}}
- credentials={credentials}
- onChange={onChange}
- tooltip="This is credentials look up"
- />
- );
- lookup = wrapper.find('Lookup');
- credLookup = wrapper.find('MultiCredentialsLookup');
});
afterEach(() => {
@@ -64,16 +52,40 @@ describe('', () => {
wrapper.unmount();
});
- test('MultiCredentialsLookup renders properly', () => {
+ test('MultiCredentialsLookup renders properly', async () => {
+ const onChange = jest.fn();
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}}
+ />
+ );
+ });
expect(wrapper.find('MultiCredentialsLookup')).toHaveLength(1);
expect(CredentialTypesAPI.read).toHaveBeenCalled();
});
test('onChange is called when you click to remove a credential from input', async () => {
- const chip = wrapper.find('PFChip').find({ isOverflowChip: false });
- const button = chip.at(1).find('ChipButton');
+ const onChange = jest.fn();
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}}
+ />
+ );
+ });
+ const chip = wrapper.find('CredentialChip');
expect(chip).toHaveLength(4);
- button.prop('onClick')();
+ const button = chip.at(1).find('ChipButton');
+ await act(async () => {
+ button.invoke('onClick')();
+ });
expect(onChange).toBeCalledWith([
{ id: 1, kind: 'cloud', name: 'Foo', url: 'www.google.com' },
{ id: 21, kind: 'vault', name: 'Gatsby' },
@@ -81,33 +93,122 @@ describe('', () => {
]);
});
- test('can change credential types', () => {
- lookup.prop('selectCategory')({}, 'Vault');
- expect(credLookup.state('selectedCredentialType')).toEqual({
- id: 500,
- key: 500,
- kind: 'vault',
- type: 'buzz',
- value: 'Vault',
- label: 'Vault',
- isDisabled: false,
+ test('should change credential types', async () => {
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}}
+ onError={() => {}}
+ />
+ );
});
- expect(CredentialsAPI.read).toHaveBeenCalled();
+ const searchButton = await waitForElement(wrapper, 'SearchButton');
+ await act(async () => {
+ searchButton.invoke('onClick')();
+ });
+ const select = await waitForElement(wrapper, 'AnsibleSelect');
+ CredentialsAPI.read.mockResolvedValueOnce({
+ data: {
+ results: [
+ { id: 1, kind: 'cloud', name: 'New Cred', url: 'www.google.com' },
+ ],
+ count: 1,
+ },
+ });
+ expect(CredentialsAPI.read).toHaveBeenCalledTimes(2);
+ await act(async () => {
+ select.invoke('onChange')({}, 500);
+ });
+ wrapper.update();
+ expect(CredentialsAPI.read).toHaveBeenCalledTimes(3);
+ expect(wrapper.find('OptionsList').prop('options')).toEqual([
+ { id: 1, kind: 'cloud', name: 'New Cred', url: 'www.google.com' },
+ ]);
});
- test('Toggle credentials only adds 1 credential per credential type except vault(see below)', () => {
- lookup.prop('onToggleItem')({ name: 'Party', id: 9, kind: 'Machine' });
+
+ test('should only add 1 credential per credential type except vault(see below)', async () => {
+ const onChange = jest.fn();
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}}
+ />
+ );
+ });
+ const searchButton = await waitForElement(wrapper, 'SearchButton');
+ await act(async () => {
+ searchButton.invoke('onClick')();
+ });
+ wrapper.update();
+ const optionsList = wrapper.find('OptionsList');
+ expect(optionsList.prop('multiple')).toEqual(false);
+ act(() => {
+ optionsList.invoke('selectItem')({
+ id: 5,
+ kind: 'Machine',
+ name: 'Cred 5',
+ url: 'www.google.com',
+ });
+ });
+ wrapper.update();
+ act(() => {
+ wrapper.find('Button[variant="primary"]').invoke('onClick')();
+ });
expect(onChange).toBeCalledWith([
{ id: 1, kind: 'cloud', name: 'Foo', url: 'www.google.com' },
{ id: 2, kind: 'ssh', name: 'Alex', url: 'www.google.com' },
{ id: 21, kind: 'vault', name: 'Gatsby' },
- { id: 9, kind: 'Machine', name: 'Party' },
+ { id: 5, kind: 'Machine', name: 'Cred 5', url: 'www.google.com' },
]);
});
- test('Toggle credentials only adds 1 credential per credential type', () => {
- lookup.prop('onToggleItem')({ name: 'Party', id: 22, kind: 'vault' });
+
+ test('should allow multiple vault credentials', async () => {
+ const onChange = jest.fn();
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}}
+ />
+ );
+ });
+ const searchButton = await waitForElement(wrapper, 'SearchButton');
+ await act(async () => {
+ searchButton.invoke('onClick')();
+ });
+ wrapper.update();
+ const typeSelect = wrapper.find('AnsibleSelect');
+ act(() => {
+ typeSelect.invoke('onChange')({}, 500);
+ });
+ wrapper.update();
+ const optionsList = wrapper.find('OptionsList');
+ expect(optionsList.prop('multiple')).toEqual(true);
+ act(() => {
+ optionsList.invoke('selectItem')({
+ id: 5,
+ kind: 'Machine',
+ name: 'Cred 5',
+ url: 'www.google.com',
+ });
+ });
+ wrapper.update();
+ act(() => {
+ wrapper.find('Button[variant="primary"]').invoke('onClick')();
+ });
expect(onChange).toBeCalledWith([
- ...credentials,
- { name: 'Party', id: 22, kind: 'vault' },
+ { id: 1, kind: 'cloud', name: 'Foo', url: 'www.google.com' },
+ { id: 2, kind: 'ssh', name: 'Alex', url: 'www.google.com' },
+ { id: 21, kind: 'vault', name: 'Gatsby' },
+ { id: 8, kind: 'Machine', name: 'Gatsby' },
+ { id: 5, kind: 'Machine', name: 'Cred 5', url: 'www.google.com' },
]);
});
});