diff --git a/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.jsx b/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.jsx index 7dd3e6b978..90f994aa6b 100644 --- a/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.jsx +++ b/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.jsx @@ -1,17 +1,40 @@ -import React from 'react'; -import { Link, useParams } from 'react-router-dom'; +import React, { useState } from 'react'; +import { Link, useHistory, useParams } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; import { Button } from '@patternfly/react-core'; +import AlertModal from '@components/AlertModal'; import { CardBody, CardActionsRow } from '@components/Card'; +import ContentLoading from '@components/ContentLoading'; +import DeleteButton from '@components/DeleteButton'; import { DetailList, Detail } from '@components/DetailList'; +import ErrorDetail from '@components/ErrorDetail'; import { formatDateString } from '@util/dates'; +import { TeamsAPI } from '@api'; function TeamDetail({ team, i18n }) { const { name, description, created, modified, summary_fields } = team; + const [deletionError, setDeletionError] = useState(null); + const [hasContentLoading, setHasContentLoading] = useState(false); + const history = useHistory(); const { id } = useParams(); + const handleDelete = async () => { + setHasContentLoading(true); + try { + await TeamsAPI.destroy(id); + history.push(`/teams`); + } catch (error) { + setDeletionError(error); + } + setHasContentLoading(false); + }; + + if (hasContentLoading) { + return ; + } + return ( @@ -36,12 +59,38 @@ function TeamDetail({ team, i18n }) { /> - {summary_fields.user_capabilities.edit && ( - - )} + {summary_fields.user_capabilities && + summary_fields.user_capabilities.edit && ( + + )} + {summary_fields.user_capabilities && + summary_fields.user_capabilities.delete && ( + + {i18n._(t`Delete`)} + + )} + {deletionError && ( + setDeletionError(null)} + > + {i18n._(t`Failed to delete team.`)} + + + )} ); } diff --git a/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.test.jsx b/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.test.jsx index 5b971c454c..420985a307 100644 --- a/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.test.jsx +++ b/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.test.jsx @@ -1,12 +1,13 @@ import React from 'react'; - +import { act } from 'react-dom/test-utils'; import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; - import TeamDetail from './TeamDetail'; +import { TeamsAPI } from '@api'; jest.mock('@api'); describe('', () => { + let wrapper; const mockTeam = { name: 'Foo', description: 'Bar', @@ -19,15 +20,25 @@ describe('', () => { }, user_capabilities: { edit: true, + delete: true, }, }, }; - test('initially renders succesfully', () => { - mountWithContexts(); + + beforeEach(async () => { + wrapper = mountWithContexts(); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); }); - test('should render Details', async done => { - const wrapper = mountWithContexts(); + afterEach(() => { + wrapper.unmount(); + }); + + test('initially renders succesfully', async () => { + await waitForElement(wrapper, 'TeamDetail'); + }); + + test('should render Details', async () => { const testParams = [ { label: 'Name', value: 'Foo' }, { label: 'Description', value: 'Bar' }, @@ -42,23 +53,52 @@ describe('', () => { expect(detail.find('dt').text()).toBe(label); expect(detail.find('dd').text()).toBe(value); } - done(); }); - test('should show edit button for users with edit permission', async done => { - const wrapper = mountWithContexts(); - const editButton = await waitForElement(wrapper, 'TeamDetail Button'); + test('should show edit button for users with edit permission', async () => { + const editButton = await waitForElement( + wrapper, + 'TeamDetail Button[aria-label="Edit"]' + ); expect(editButton.text()).toEqual('Edit'); expect(editButton.prop('to')).toBe('/teams/undefined/edit'); - done(); }); - test('should hide edit button for users without edit permission', async done => { + test('should hide edit button for users without edit permission', async () => { const readOnlyTeam = { ...mockTeam }; readOnlyTeam.summary_fields.user_capabilities.edit = false; - const wrapper = mountWithContexts(); + wrapper = mountWithContexts(); await waitForElement(wrapper, 'TeamDetail'); - expect(wrapper.find('TeamDetail Button').length).toBe(0); - done(); + expect(wrapper.find('TeamDetail Button[aria-label="Edit"]').length).toBe(0); + }); + + test('expected api call is made for delete', async () => { + await waitForElement(wrapper, 'TeamDetail Button[aria-label="Delete"]'); + await act(async () => { + wrapper.find('DeleteButton').invoke('onConfirm')(); + }); + expect(TeamsAPI.destroy).toHaveBeenCalledTimes(1); + }); + + test('Error dialog shown for failed deletion', async () => { + TeamsAPI.destroy.mockImplementationOnce(() => Promise.reject(new Error())); + wrapper = mountWithContexts(); + await waitForElement(wrapper, 'TeamDetail Button[aria-label="Delete"]'); + await act(async () => { + wrapper.find('DeleteButton').invoke('onConfirm')(); + }); + await waitForElement( + wrapper, + 'Modal[title="Error!"]', + el => el.length === 1 + ); + await act(async () => { + wrapper.find('Modal[title="Error!"]').invoke('onClose')(); + }); + await waitForElement( + wrapper, + 'Modal[title="Error!"]', + el => el.length === 0 + ); }); });