diff --git a/awx/ui_next/src/screens/Application/Application/Application.test.jsx b/awx/ui_next/src/screens/Application/Application/Application.test.jsx
index 8b8f8fba5bde..136c468075fb 100644
--- a/awx/ui_next/src/screens/Application/Application/Application.test.jsx
+++ b/awx/ui_next/src/screens/Application/Application/Application.test.jsx
@@ -58,6 +58,7 @@ describe('', () => {
expect(ApplicationsAPI.readDetail).toBeCalledWith(1);
});
test('should throw error', async () => {
+ ApplicationsAPI.readOptions.mockResolvedValue(options);
ApplicationsAPI.readDetail.mockRejectedValue(
new Error({
response: {
@@ -70,7 +71,6 @@ describe('', () => {
},
})
);
- ApplicationsAPI.readOptions.mockResolvedValue(options);
await act(async () => {
wrapper = mountWithContexts( {}} />);
});
diff --git a/awx/ui_next/src/screens/Application/ApplicationEdit/ApplicationEdit.jsx b/awx/ui_next/src/screens/Application/ApplicationEdit/ApplicationEdit.jsx
index e72f93b68101..18268ba63a41 100644
--- a/awx/ui_next/src/screens/Application/ApplicationEdit/ApplicationEdit.jsx
+++ b/awx/ui_next/src/screens/Application/ApplicationEdit/ApplicationEdit.jsx
@@ -1,11 +1,50 @@
-import React from 'react';
+import React, { useState } from 'react';
+import { useHistory, useParams } from 'react-router-dom';
+
import { Card, PageSection } from '@patternfly/react-core';
+import ApplicationForm from '../shared/ApplicationForm';
+import { ApplicationsAPI } from '../../../api';
+import { CardBody } from '../../../components/Card';
+
+function ApplicationEdit({
+ application,
+ authorizationOptions,
+ clientTypeOptions,
+}) {
+ const history = useHistory();
+ const { id } = useParams();
+ const [submitError, setSubmitError] = useState(null);
+
+ const handleSubmit = async ({ ...values }) => {
+ values.organization = values.organization.id;
+ try {
+ await ApplicationsAPI.update(id, values);
+ history.push(`/applications/${id}/details`);
+ } catch (err) {
+ setSubmitError(err);
+ }
+ };
-function ApplicationEdit() {
+ const handleCancel = () => {
+ history.push(`/applications/${id}/details`);
+ };
return (
-
- Application Edit
-
+ <>
+
+
+
+
+
+
+
+ >
);
}
export default ApplicationEdit;
diff --git a/awx/ui_next/src/screens/Application/ApplicationEdit/ApplicationEdit.test.jsx b/awx/ui_next/src/screens/Application/ApplicationEdit/ApplicationEdit.test.jsx
new file mode 100644
index 000000000000..e3dd4b03ea41
--- /dev/null
+++ b/awx/ui_next/src/screens/Application/ApplicationEdit/ApplicationEdit.test.jsx
@@ -0,0 +1,224 @@
+import React from 'react';
+import { act } from 'react-dom/test-utils';
+
+import { createMemoryHistory } from 'history';
+import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
+
+import { ApplicationsAPI } from '../../../api';
+import ApplicationEdit from './ApplicationEdit';
+
+jest.mock('../../../api/models/Applications');
+jest.mock('../../../api/models/Organizations');
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ history: () => ({
+ location: '/applications/1/edit',
+ }),
+ useParams: () => ({ id: 1 }),
+}));
+
+const authorizationOptions = [
+ {
+ key: 'authorization-code',
+ label: 'Authorization code',
+ value: 'authorization-code',
+ },
+ {
+ key: 'password',
+ label: 'Resource owner password-based',
+ value: 'password',
+ },
+];
+
+const clientTypeOptions = [
+ { key: 'confidential', label: 'Confidential', value: 'confidential' },
+ { key: 'public', label: 'Public', value: 'public' },
+];
+
+const application = {
+ id: 1,
+ type: 'o_auth2_application',
+ url: '/api/v2/applications/10/',
+ related: {
+ named_url: '/api/v2/applications/Alex++bar/',
+ tokens: '/api/v2/applications/10/tokens/',
+ activity_stream: '/api/v2/applications/10/activity_stream/',
+ },
+ summary_fields: {
+ organization: {
+ id: 230,
+ name: 'bar',
+ description:
+ 'SaleNameBedPersonalityManagerWhileFinanceBreakToothPersoné²',
+ },
+ user_capabilities: {
+ edit: true,
+ delete: true,
+ },
+ tokens: {
+ count: 0,
+ results: [],
+ },
+ },
+ created: '2020-06-11T17:54:33.983993Z',
+ modified: '2020-06-11T17:54:33.984039Z',
+ name: 'Alex',
+ description: '',
+ client_id: 'b1dmj8xzkbFm1ZQ27ygw2ZeE9I0AXqqeL74fiyk4',
+ client_secret: '************',
+ client_type: 'confidential',
+ redirect_uris: 'http://www.google.com',
+ authorization_grant_type: 'authorization-code',
+ skip_authorization: false,
+ organization: 230,
+};
+
+describe('', () => {
+ let wrapper;
+ test('should mount properly', async () => {
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+ );
+ });
+ expect(wrapper.find('ApplicationEdit').length).toBe(1);
+ });
+ test('should cancel form properly', async () => {
+ const history = createMemoryHistory({
+ initialEntries: ['/applications/1/edit'],
+ });
+
+ await act(async () => {
+ wrapper = mountWithContexts(
+ ,
+ {
+ context: { router: { history } },
+ }
+ );
+ await act(async () => {
+ wrapper.find('Button[aria-label="Cancel"]').prop('onClick')();
+ });
+ expect(history.location.pathname).toBe('/applications/1/details');
+ });
+ });
+ test('should throw error on submit', async () => {
+ const error = {
+ response: {
+ config: {
+ method: 'patch',
+ url: '/api/v2/applications/',
+ },
+ data: { detail: 'An error occurred' },
+ },
+ };
+ ApplicationsAPI.update.mockRejectedValue(error);
+ const history = createMemoryHistory({
+ initialEntries: ['/applications/1/edit'],
+ });
+
+ await act(async () => {
+ wrapper = mountWithContexts(
+ ,
+ {
+ context: { router: { history } },
+ }
+ );
+ });
+ await act(async () => {
+ wrapper.find('Formik').prop('onSubmit')({
+ id: 1,
+ organization: { id: 4 },
+ });
+ });
+
+ wrapper.update();
+ expect(wrapper.find('FormSubmitError').length).toBe(1);
+ });
+ test('expect values to be updated and submitted properly', async () => {
+ const history = createMemoryHistory({
+ initialEntries: ['/applications/1/edit'],
+ });
+
+ await act(async () => {
+ wrapper = mountWithContexts(
+ ,
+ {
+ context: { router: { history } },
+ }
+ );
+ });
+ await act(async () => {
+ wrapper.find('input#name').simulate('change', {
+ target: { value: 'new foo', name: 'name' },
+ });
+ wrapper.find('input#description').simulate('change', {
+ target: { value: 'new bar', name: 'description' },
+ });
+
+ wrapper.find('input#redirect_uris').simulate('change', {
+ target: { value: 'https://www.google.com', name: 'redirect_uris' },
+ });
+ wrapper.find('AnsibleSelect[name="client_type"]').prop('onChange')(
+ {},
+ 'confidential'
+ );
+ wrapper.find('OrganizationLookup').invoke('onChange')({
+ id: 1,
+ name: 'organization',
+ });
+ });
+
+ wrapper.update();
+ expect(wrapper.find('input#name').prop('value')).toBe('new foo');
+ expect(wrapper.find('input#description').prop('value')).toBe('new bar');
+ expect(wrapper.find('Chip').length).toBe(1);
+ expect(wrapper.find('Chip').text()).toBe('organization');
+ expect(
+ wrapper
+ .find('AnsibleSelect[name="authorization_grant_type"]')
+ .prop('value')
+ ).toBe('authorization-code');
+ expect(
+ wrapper.find('AnsibleSelect[name="client_type"]').prop('value')
+ ).toBe('confidential');
+ expect(wrapper.find('input#redirect_uris').prop('value')).toBe(
+ 'https://www.google.com'
+ );
+ await act(async () => {
+ wrapper.find('Formik').prop('onSubmit')({
+ authorization_grant_type: 'authorization-code',
+ client_type: 'confidential',
+ description: 'bar',
+ name: 'foo',
+ organization: { id: 1 },
+ redirect_uris: 'http://www.google.com',
+ });
+ });
+
+ expect(ApplicationsAPI.update).toBeCalledWith(1, {
+ authorization_grant_type: 'authorization-code',
+ client_type: 'confidential',
+ description: 'bar',
+ name: 'foo',
+ organization: 1,
+ redirect_uris: 'http://www.google.com',
+ });
+ });
+});
diff --git a/awx/ui_next/src/screens/Application/Applications.jsx b/awx/ui_next/src/screens/Application/Applications.jsx
index d25245095efc..1842e5bd89b5 100644
--- a/awx/ui_next/src/screens/Application/Applications.jsx
+++ b/awx/ui_next/src/screens/Application/Applications.jsx
@@ -19,7 +19,6 @@ function Applications({ i18n }) {
if (!application) {
return;
}
-
setBreadcrumbConfig({
'/applications': i18n._(t`Applications`),
'/applications/add': i18n._(t`Create New Application`),
diff --git a/awx/ui_next/src/screens/Application/shared/ApplicationForm.test.jsx b/awx/ui_next/src/screens/Application/shared/ApplicationForm.test.jsx
index f43fc878d238..e7e950e3f53e 100644
--- a/awx/ui_next/src/screens/Application/shared/ApplicationForm.test.jsx
+++ b/awx/ui_next/src/screens/Application/shared/ApplicationForm.test.jsx
@@ -178,4 +178,62 @@ describe(' {
redirect_uris: 'http://www.google.com',
});
});
+ test('should render required on Redirect URIs', async () => {
+ OrganizationsAPI.read.mockResolvedValue({
+ results: [{ id: 1 }, { id: 2 }],
+ });
+ const onSubmit = jest.fn();
+ const onCancel = jest.fn();
+ const application = {
+ id: 1,
+ type: 'o_auth2_application',
+ url: '/api/v2/applications/10/',
+ related: {
+ named_url: '/api/v2/applications/Alex++bar/',
+ tokens: '/api/v2/applications/10/tokens/',
+ activity_stream: '/api/v2/applications/10/activity_stream/',
+ },
+ summary_fields: {
+ organization: {
+ id: 230,
+ name: 'bar',
+ description:
+ 'SaleNameBedPersonalityManagerWhileFinanceBreakToothPersoné²',
+ },
+ user_capabilities: {
+ edit: true,
+ delete: true,
+ },
+ tokens: {
+ count: 0,
+ results: [],
+ },
+ },
+ created: '2020-06-11T17:54:33.983993Z',
+ modified: '2020-06-11T17:54:33.984039Z',
+ name: 'Alex',
+ description: '',
+ client_id: 'b1dmj8xzkbFm1ZQ27ygw2ZeE9I0AXqqeL74fiyk4',
+ client_secret: '************',
+ client_type: 'confidential',
+ redirect_uris: 'http://www.google.com',
+ authorization_grant_type: 'authorization-code',
+ skip_authorization: false,
+ organization: 230,
+ };
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+ );
+ });
+ expect(
+ wrapper.find('FormField[name="redirect_uris"]').prop('isRequired')
+ ).toBe(true);
+ });
});