From 4c555815b35fefb2bb7d914e08395deca2cb50b8 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Mon, 10 Aug 2020 16:23:54 -0700 Subject: [PATCH] add notification list tests --- .../StatusLabel/StatusLabel.test.jsx | 61 ++++++ .../NotificationTemplateDetail.jsx | 20 +- .../NotificationTemplateList.test.jsx | 202 ++++++++++++++++++ .../NotificationTemplateListItem.jsx | 11 +- .../NotificationTemplateListItem.test.jsx | 64 ++++++ .../NotificationTemplates.test.jsx | 6 - 6 files changed, 336 insertions(+), 28 deletions(-) create mode 100644 awx/ui_next/src/components/StatusLabel/StatusLabel.test.jsx create mode 100644 awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.test.jsx create mode 100644 awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.test.jsx diff --git a/awx/ui_next/src/components/StatusLabel/StatusLabel.test.jsx b/awx/ui_next/src/components/StatusLabel/StatusLabel.test.jsx new file mode 100644 index 0000000000..58fb6c1a28 --- /dev/null +++ b/awx/ui_next/src/components/StatusLabel/StatusLabel.test.jsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import StatusLabel from './StatusLabel'; + +describe('StatusLabel', () => { + test('should render success', () => { + const wrapper = mount(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('CheckCircleIcon')).toHaveLength(1); + expect(wrapper.find('Label').prop('color')).toEqual('green'); + expect(wrapper.text()).toEqual('Success'); + }); + + test('should render failed', () => { + const wrapper = mount(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1); + expect(wrapper.find('Label').prop('color')).toEqual('red'); + expect(wrapper.text()).toEqual('Failed'); + }); + + test('should render error', () => { + const wrapper = mount(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('ExclamationCircleIcon')).toHaveLength(1); + expect(wrapper.find('Label').prop('color')).toEqual('red'); + expect(wrapper.text()).toEqual('Error'); + }); + + test('should render running', () => { + const wrapper = mount(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('SyncAltIcon')).toHaveLength(1); + expect(wrapper.find('Label').prop('color')).toEqual('blue'); + expect(wrapper.text()).toEqual('Running'); + }); + + test('should render pending', () => { + const wrapper = mount(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('ClockIcon')).toHaveLength(1); + expect(wrapper.find('Label').prop('color')).toEqual('blue'); + expect(wrapper.text()).toEqual('Pending'); + }); + + test('should render waiting', () => { + const wrapper = mount(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('ClockIcon')).toHaveLength(1); + expect(wrapper.find('Label').prop('color')).toEqual('grey'); + expect(wrapper.text()).toEqual('Waiting'); + }); + + test('should render canceled', () => { + const wrapper = mount(); + expect(wrapper).toHaveLength(1); + expect(wrapper.find('ExclamationTriangleIcon')).toHaveLength(1); + expect(wrapper.find('Label').prop('color')).toEqual('orange'); + expect(wrapper.text()).toEqual('Canceled'); + }); +}); diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.jsx index 18f45a4c21..d7f37f9fab 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.jsx @@ -1,33 +1,17 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useCallback } from 'react'; import { Link, useHistory } from 'react-router-dom'; import { withI18n } from '@lingui/react'; -import { - Button, - Chip, - TextList, - TextListItem, - TextListItemVariants, - TextListVariants, - Label, -} from '@patternfly/react-core'; +import { Button } from '@patternfly/react-core'; import { t } from '@lingui/macro'; - import AlertModal from '../../../components/AlertModal'; import { CardBody, CardActionsRow } from '../../../components/Card'; -import ChipGroup from '../../../components/ChipGroup'; -import ContentError from '../../../components/ContentError'; -import ContentLoading from '../../../components/ContentLoading'; -import CredentialChip from '../../../components/CredentialChip'; import { Detail, DetailList, DeletedDetail, - UserDateDetail, } from '../../../components/DetailList'; import DeleteButton from '../../../components/DeleteButton'; import ErrorDetail from '../../../components/ErrorDetail'; -import LaunchButton from '../../../components/LaunchButton'; -import { VariablesDetail } from '../../../components/CodeMirrorInput'; import { NotificationTemplatesAPI } from '../../../api'; import useRequest, { useDismissableError } from '../../../util/useRequest'; import { NOTIFICATION_TYPES } from '../constants'; diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.test.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.test.jsx new file mode 100644 index 0000000000..d39bffe087 --- /dev/null +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.test.jsx @@ -0,0 +1,202 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { OrganizationsAPI } from '../../../api'; +import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; +import NotificationTemplateList from './NotificationTemplateList'; + +jest.mock('../../../api'); + +const mockTemplates = { + data: { + count: 3, + results: [ + { + name: 'Boston', + id: 1, + url: '/notification_templates/1', + type: 'slack', + summary_fields: { + recent_notifications: [ + { + status: 'success', + }, + ], + user_capabilities: { + delete: true, + edit: true, + }, + }, + }, + { + name: 'Minneapolis', + id: 2, + url: '/notification_templates/2', + summary_fields: { + recent_notifications: [], + user_capabilities: { + delete: true, + edit: true, + }, + }, + }, + { + name: 'Philidelphia', + id: 3, + url: '/notification_templates/3', + summary_fields: { + recent_notifications: [ + { + status: 'failed', + }, + { + status: 'success', + }, + ], + user_capabilities: { + delete: true, + edit: true, + }, + }, + }, + ], + }, +}; + +describe('', () => { + let wrapper; + beforeEach(() => { + OrganizationsAPI.read.mockResolvedValue(mockTemplates); + OrganizationsAPI.readOptions.mockResolvedValue({ + data: { + actions: { + GET: {}, + POST: {}, + }, + }, + }); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should load notifications', async () => { + await act(async () => { + wrapper = mountWithContexts(); + }); + wrapper.update(); + expect(OrganizationsAPI.read).toHaveBeenCalledTimes(1); + expect(wrapper.find('NotificationTemplateListItem').length).toBe(3); + }); + + test('should select item', async () => { + const itemCheckboxInput = 'input#select-template-1'; + await act(async () => { + wrapper = mountWithContexts(); + }); + wrapper.update(); + expect(wrapper.find(itemCheckboxInput).prop('checked')).toEqual(false); + await act(async () => { + wrapper + .find(itemCheckboxInput) + .closest('DataListCheck') + .props() + .onChange(); + }); + wrapper.update(); + expect(wrapper.find(itemCheckboxInput).prop('checked')).toEqual(true); + }); + + test('should delete notifications', async () => { + await act(async () => { + wrapper = mountWithContexts(); + }); + wrapper.update(); + expect(OrganizationsAPI.read).toHaveBeenCalledTimes(1); + await act(async () => { + wrapper + .find('Checkbox#select-all') + .props() + .onChange(true); + }); + wrapper.update(); + await act(async () => { + wrapper.find('button[aria-label="Delete"]').simulate('click'); + wrapper.update(); + }); + const deleteButton = global.document.querySelector( + 'body div[role="dialog"] button[aria-label="confirm delete"]' + ); + expect(deleteButton).not.toEqual(null); + await act(async () => { + deleteButton.click(); + }); + expect(OrganizationsAPI.destroy).toHaveBeenCalledTimes(3); + expect(OrganizationsAPI.read).toHaveBeenCalledTimes(2); + }); + + test('should show error dialog shown for failed deletion', async () => { + const itemCheckboxInput = 'input#select-template-1'; + OrganizationsAPI.destroy.mockRejectedValue( + new Error({ + response: { + config: { + method: 'delete', + url: '/api/v2/organizations/1', + }, + data: 'An error occurred', + }, + }) + ); + await act(async () => { + wrapper = mountWithContexts(); + }); + wrapper.update(); + await act(async () => { + wrapper + .find(itemCheckboxInput) + .closest('DataListCheck') + .props() + .onChange(); + }); + wrapper.update(); + await act(async () => { + wrapper.find('button[aria-label="Delete"]').simulate('click'); + wrapper.update(); + }); + const deleteButton = global.document.querySelector( + 'body div[role="dialog"] button[aria-label="confirm delete"]' + ); + expect(deleteButton).not.toEqual(null); + await act(async () => { + deleteButton.click(); + }); + wrapper.update(); + + const modal = wrapper.find('Modal'); + expect(modal.prop('isOpen')).toEqual(true); + expect(modal.prop('title')).toEqual('Error!'); + }); + + test('should show add button', async () => { + await act(async () => { + wrapper = mountWithContexts(); + }); + wrapper.update(); + expect(wrapper.find('ToolbarAddButton').length).toBe(1); + }); + + test('should hide add button (rbac)', async () => { + OrganizationsAPI.readOptions.mockResolvedValue({ + data: { + actions: { + GET: {}, + }, + }, + }); + await act(async () => { + wrapper = mountWithContexts(); + }); + wrapper.update(); + expect(wrapper.find('ToolbarAddButton').length).toBe(0); + }); +}); diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.jsx index ed26638ed6..0087e7f9a8 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.jsx @@ -34,7 +34,10 @@ function NotificationTemplateListItem({ onSelect, i18n, }) { - const latestStatus = template.summary_fields?.recent_notifications[0]?.status; + const recentNotifications = template.summary_fields?.recent_notifications; + const latestStatus = recentNotifications + ? recentNotifications[0]?.status + : null; const [status, setStatus] = useState(latestStatus); useEffect(() => { @@ -44,7 +47,7 @@ function NotificationTemplateListItem({ const { request: sendTestNotification, isLoading, error } = useRequest( useCallback(() => { NotificationTemplatesAPI.test(template.id); - setStatus('pending'); + setStatus('running'); }, [template.id]) ); @@ -72,11 +75,11 @@ function NotificationTemplateListItem({ {template.name} , - + {status && } , - {i18n._(t`Type`)} + {i18n._(t`Type:`)}{' '} {NOTIFICATION_TYPES[template.notification_type] || template.notification_type} , diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.test.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.test.jsx new file mode 100644 index 0000000000..5a4566779e --- /dev/null +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateListItem.test.jsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; +import { NotificationTemplatesAPI } from '../../../api'; +import NotificationTemplateListItem from './NotificationTemplateListItem'; + +jest.mock('../../../api/models/NotificationTemplates'); + +const template = { + id: 3, + notification_type: 'slack', + name: 'Test Notification', + summary_fields: { + user_capabilities: { + edit: true, + }, + recent_notifications: [ + { + status: 'success', + }, + ], + }, +}; + +describe('', () => { + test('should render template row', () => { + const wrapper = mountWithContexts( + + ); + + const cells = wrapper.find('DataListCell'); + expect(cells).toHaveLength(3); + expect(cells.at(0).text()).toEqual('Test Notification'); + expect(cells.at(1).text()).toEqual('Success'); + expect(cells.at(2).text()).toEqual('Type: Slack'); + }); + + test('should send test notification', async () => { + NotificationTemplatesAPI.test.mockResolvedValue({}); + + const wrapper = mountWithContexts( + + ); + await act(async () => { + wrapper + .find('Button') + .at(0) + .invoke('onClick')(); + }); + expect(NotificationTemplatesAPI.test).toHaveBeenCalledTimes(1); + expect( + wrapper + .find('DataListCell') + .at(1) + .text() + ).toEqual('Running'); + }); +}); diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplates.test.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplates.test.jsx index 93babc8e06..9333850cf9 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplates.test.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplates.test.jsx @@ -1,18 +1,14 @@ import React from 'react'; - import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; - import NotificationTemplates from './NotificationTemplates'; describe('', () => { let pageWrapper; let pageSections; - let title; beforeEach(() => { pageWrapper = mountWithContexts(); pageSections = pageWrapper.find('PageSection'); - title = pageWrapper.find('Title'); }); afterEach(() => { @@ -22,8 +18,6 @@ describe('', () => { test('initially renders without crashing', () => { expect(pageWrapper.length).toBe(1); expect(pageSections.length).toBe(2); - expect(title.length).toBe(1); - expect(title.props().size).toBe('2xl'); expect(pageSections.first().props().variant).toBe('light'); }); });