diff --git a/awx/ui_next/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.jsx b/awx/ui_next/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.jsx index 39c1b4dfbf..f8ce4879c8 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryGroupDetail/InventoryGroupDetail.jsx @@ -17,7 +17,7 @@ import InventoryGroupsDeleteModal from '../shared/InventoryGroupsDeleteModal'; function InventoryGroupDetail({ i18n, inventoryGroup }) { const { - summary_fields: { created_by, modified_by }, + summary_fields: { created_by, modified_by, user_capabilities }, created, modified, name, @@ -54,24 +54,28 @@ function InventoryGroupDetail({ i18n, inventoryGroup }) { /> - - - history.push(`/inventories/inventory/${params.id}/groups`) - } - /> + {user_capabilities?.edit && ( + + )} + {user_capabilities?.delete && ( + + history.push(`/inventories/inventory/${params.id}/groups`) + } + /> + )} {error && ( ', () => { let wrapper; let history; - beforeEach(async () => { - await act(async () => { - history = createMemoryHistory({ - initialEntries: ['/inventories/inventory/1/groups/1/details'], - }); - wrapper = mountWithContexts( - - - , - { - context: { - router: { - history, - route: { - location: history.location, - match: { params: { id: 1 } }, + + describe('User has full permissions', () => { + beforeEach(async () => { + await act(async () => { + history = createMemoryHistory({ + initialEntries: ['/inventories/inventory/1/groups/1/details'], + }); + wrapper = mountWithContexts( + + + , + { + context: { + router: { + history, + route: { + location: history.location, + match: { params: { id: 1 } }, + }, }, }, - }, - } + } + ); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + }); + }); + afterEach(() => { + wrapper.unmount(); + jest.clearAllMocks(); + }); + test('InventoryGroupDetail renders successfully', () => { + expect(wrapper.length).toBe(1); + }); + + test('should open delete modal and then call api to delete the group', async () => { + await act(async () => { + wrapper.find('button[aria-label="Delete"]').simulate('click'); + }); + await waitForElement(wrapper, 'Modal', el => el.length === 1); + expect(wrapper.find('Modal').length).toBe(1); + await act(async () => { + wrapper.find('Radio[id="radio-delete"]').invoke('onChange')(); + }); + wrapper.update(); + expect( + wrapper.find('Button[aria-label="Confirm Delete"]').prop('isDisabled') + ).toBe(false); + await act(() => + wrapper.find('Button[aria-label="Confirm Delete"]').prop('onClick')() ); - await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + expect(GroupsAPI.destroy).toBeCalledWith(1); + }); + + test('should navigate user to edit form on edit button click', async () => { + wrapper.find('button[aria-label="Edit"]').simulate('click'); + expect(history.location.pathname).toEqual( + '/inventories/inventory/1/groups/1/edit' + ); + }); + + test('details should render with the proper values and action buttons shown', () => { + expect(wrapper.find('Detail[label="Name"]').prop('value')).toBe('Foo'); + expect(wrapper.find('Detail[label="Description"]').prop('value')).toBe( + 'Bar' + ); + expect(wrapper.find('Detail[label="Created"]').length).toBe(1); + expect(wrapper.find('Detail[label="Last Modified"]').length).toBe(1); + expect(wrapper.find('VariablesDetail').prop('value')).toBe('bizz: buzz'); + + expect(wrapper.find('button[aria-label="Edit"]').length).toBe(1); + expect(wrapper.find('button[aria-label="Delete"]').length).toBe(1); }); }); - afterEach(() => { - wrapper.unmount(); - jest.clearAllMocks(); - }); - test('InventoryGroupDetail renders successfully', () => { - expect(wrapper.length).toBe(1); - }); - test('should open delete modal and then call api to delete the group', async () => { - await act(async () => { - wrapper.find('button[aria-label="Delete"]').simulate('click'); + describe('User has read-only permissions', () => { + test('should hide edit/delete buttons', async () => { + const readOnlyGroup = { + ...inventoryGroup, + summary_fields: { + ...inventoryGroup.summary_fields, + user_capabilities: { + delete: false, + edit: false, + }, + }, + }; + + await act(async () => { + history = createMemoryHistory({ + initialEntries: ['/inventories/inventory/1/groups/1/details'], + }); + wrapper = mountWithContexts( + + + , + { + context: { + router: { + history, + route: { + location: history.location, + match: { params: { id: 1 } }, + }, + }, + }, + } + ); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + }); + + expect(wrapper.find('button[aria-label="Edit"]').length).toBe(0); + expect(wrapper.find('button[aria-label="Delete"]').length).toBe(0); + + wrapper.unmount(); }); - await waitForElement(wrapper, 'Modal', el => el.length === 1); - expect(wrapper.find('Modal').length).toBe(1); - await act(async () => { - wrapper.find('Radio[id="radio-delete"]').invoke('onChange')(); - }); - wrapper.update(); - expect( - wrapper.find('Button[aria-label="Confirm Delete"]').prop('isDisabled') - ).toBe(false); - await act(() => - wrapper.find('Button[aria-label="Confirm Delete"]').prop('onClick')() - ); - expect(GroupsAPI.destroy).toBeCalledWith(1); - }); - - test('should navigate user to edit form on edit button click', async () => { - wrapper.find('button[aria-label="Edit"]').simulate('click'); - expect(history.location.pathname).toEqual( - '/inventories/inventory/1/groups/1/edit' - ); - }); - - test('details should render with the proper values', () => { - expect(wrapper.find('Detail[label="Name"]').prop('value')).toBe('Foo'); - expect(wrapper.find('Detail[label="Description"]').prop('value')).toBe( - 'Bar' - ); - expect(wrapper.find('Detail[label="Created"]').length).toBe(1); - expect(wrapper.find('Detail[label="Last Modified"]').length).toBe(1); - expect(wrapper.find('VariablesDetail').prop('value')).toBe('bizz: buzz'); }); });