diff --git a/__tests__/components/DataListToolbar.test.jsx b/__tests__/components/DataListToolbar.test.jsx index facc3b1128..958573e2f1 100644 --- a/__tests__/components/DataListToolbar.test.jsx +++ b/__tests__/components/DataListToolbar.test.jsx @@ -36,6 +36,7 @@ describe('', () => { onSearch={onSearch} onSort={onSort} onSelectAll={onSelectAll} + showSelectAll /> ); diff --git a/__tests__/components/NotificationList.test.jsx b/__tests__/components/NotificationList.test.jsx new file mode 100644 index 0000000000..893f7a58a0 --- /dev/null +++ b/__tests__/components/NotificationList.test.jsx @@ -0,0 +1,160 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { MemoryRouter } from 'react-router-dom'; +import { I18nProvider } from '@lingui/react'; +import Notifications from '../../src/components/NotificationsList/Notifications.list'; + +describe('', () => { + test('initially renders succesfully', () => { + mount( + + + + + + ); + }); + test('fetches notifications on mount', () => { + const spy = jest.spyOn(Notifications.prototype, 'fetchNotifications'); + mount( + + + + + + ); + expect(spy).toHaveBeenCalled(); + }); + test('toggle success calls post', () => { + const spy = jest.spyOn(Notifications.prototype, 'postToSuccess'); + const wrapper = mount( + + + + + + ).find('Notifications'); + wrapper.instance().toggleNotification(1, true, 'success'); + expect(spy).toHaveBeenCalledWith(1, true); + }); + test('post success makes request and updates state properly', async () => { + const postSuccessFn = jest.fn(); + const wrapper = mount( + + + + + + ).find('Notifications'); + wrapper.setState({ successTemplateIds: [44] }); + await wrapper.instance().postToSuccess(44, true); + expect(postSuccessFn).toHaveBeenCalledWith(1, { id: 44, disassociate: true }); + expect(wrapper.state('successTemplateIds')).not.toContain(44); + await wrapper.instance().postToSuccess(44, false); + expect(postSuccessFn).toHaveBeenCalledWith(1, { id: 44 }); + expect(wrapper.state('successTemplateIds')).toContain(44); + }); + test('toggle error calls post', () => { + const spy = jest.spyOn(Notifications.prototype, 'postToError'); + const wrapper = mount( + + + + + + ).find('Notifications'); + wrapper.instance().toggleNotification(1, true, 'error'); + expect(spy).toHaveBeenCalledWith(1, true); + }); + test('post error makes request and updates state properly', async () => { + const postErrorFn = jest.fn(); + const wrapper = mount( + + + + + + ).find('Notifications'); + wrapper.setState({ errorTemplateIds: [44] }); + await wrapper.instance().postToError(44, true); + expect(postErrorFn).toHaveBeenCalledWith(1, { id: 44, disassociate: true }); + expect(wrapper.state('errorTemplateIds')).not.toContain(44); + await wrapper.instance().postToError(44, false); + expect(postErrorFn).toHaveBeenCalledWith(1, { id: 44 }); + expect(wrapper.state('errorTemplateIds')).toContain(44); + }); + test('fetchNotifications', async () => { + const mockQueryParams = { + page: 44, + page_size: 10, + order_by: 'name' + }; + const getNotificationsFn = jest.fn().mockResolvedValue({ + data: { + results: [ + { id: 1 }, + { id: 2 }, + { id: 3 } + ] + } + }); + const getSuccessFn = jest.fn().mockResolvedValue({ + data: { + results: [ + { id: 1 } + ] + } + }); + const getErrorFn = jest.fn().mockResolvedValue({ + data: { + results: [ + { id: 2 } + ] + } + }); + const wrapper = mount( + + + + + + ).find('Notifications'); + wrapper.instance().updateUrl = jest.fn(); + await wrapper.instance().fetchNotifications(mockQueryParams); + expect(getNotificationsFn).toHaveBeenCalledWith(1, mockQueryParams); + expect(getSuccessFn).toHaveBeenCalledWith(1, { + id__in: '1,2,3' + }); + expect(getErrorFn).toHaveBeenCalledWith(1, { + id__in: '1,2,3' + }); + expect(wrapper.state('successTemplateIds')).toContain(1); + expect(wrapper.state('errorTemplateIds')).toContain(2); + }); +}); diff --git a/__tests__/components/NotificationListItem.test.jsx b/__tests__/components/NotificationListItem.test.jsx new file mode 100644 index 0000000000..f162782338 --- /dev/null +++ b/__tests__/components/NotificationListItem.test.jsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { MemoryRouter } from 'react-router-dom'; +import { I18nProvider } from '@lingui/react'; +import NotificationListItem from '../../src/components/NotificationsList/NotificationListItem'; + +describe('', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + }); + + test('initially renders succesfully', () => { + wrapper = mount( + + + + + + ); + expect(wrapper.length).toBe(1); + }); + + test('handles success click when toggle is on', () => { + const toggleNotification = jest.fn(); + wrapper = mount( + + + + + + ); + wrapper.find('Switch').first().find('input').simulate('change'); + expect(toggleNotification).toHaveBeenCalledWith(9000, true, 'success'); + }); + + test('handles success click when toggle is off', () => { + const toggleNotification = jest.fn(); + wrapper = mount( + + + + + + ); + wrapper.find('Switch').first().find('input').simulate('change'); + expect(toggleNotification).toHaveBeenCalledWith(9000, false, 'success'); + }); + + test('handles error click when toggle is on', () => { + const toggleNotification = jest.fn(); + wrapper = mount( + + + + + + ); + wrapper.find('Switch').at(1).find('input').simulate('change'); + expect(toggleNotification).toHaveBeenCalledWith(9000, true, 'error'); + }); + + test('handles error click when toggle is off', () => { + const toggleNotification = jest.fn(); + wrapper = mount( + + + + + + ); + wrapper.find('Switch').at(1).find('input').simulate('change'); + expect(toggleNotification).toHaveBeenCalledWith(9000, false, 'error'); + }); +}); diff --git a/__tests__/pages/Organizations/screens/Organization/OrganizationDetail.test.jsx b/__tests__/pages/Organizations/screens/Organization/OrganizationDetail.test.jsx index aaaa7adde4..7262ae85a9 100644 --- a/__tests__/pages/Organizations/screens/Organization/OrganizationDetail.test.jsx +++ b/__tests__/pages/Organizations/screens/Organization/OrganizationDetail.test.jsx @@ -12,6 +12,7 @@ describe('', () => { diff --git a/__tests__/pages/Organizations/screens/Organization/OrganizationNotifications.jsx b/__tests__/pages/Organizations/screens/Organization/OrganizationNotifications.jsx new file mode 100644 index 0000000000..9716eadf8a --- /dev/null +++ b/__tests__/pages/Organizations/screens/Organization/OrganizationNotifications.jsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { MemoryRouter } from 'react-router-dom'; +import OrganizationNotifications from '../../../../../src/pages/Organizations/screens/Organization/OrganizationNotifications'; + +describe('', () => { + test('initially renders succesfully', () => { + mount( + + + + ); + }); + test('handles api requests', () => { + const getOrganizationNotifications = jest.fn(); + const getOrganizationNotificationSuccess = jest.fn(); + const getOrganizationNotificationError = jest.fn(); + const createOrganizationNotificationSuccess = jest.fn(); + const createOrganizationNotificationError = jest.fn(); + const wrapper = mount( + + + + ).find('OrganizationNotifications'); + wrapper.instance().getOrgNotifications(1, { foo: 'bar' }); + expect(getOrganizationNotifications).toHaveBeenCalledWith(1, { foo: 'bar' }); + wrapper.instance().getOrgNotificationSuccess(1, { foo: 'bar' }); + expect(getOrganizationNotificationSuccess).toHaveBeenCalledWith(1, { foo: 'bar' }); + wrapper.instance().getOrgNotificationError(1, { foo: 'bar' }); + expect(getOrganizationNotificationError).toHaveBeenCalledWith(1, { foo: 'bar' }); + wrapper.instance().createOrgNotificationSuccess(1, { id: 2 }); + expect(createOrganizationNotificationSuccess).toHaveBeenCalledWith(1, { id: 2 }); + wrapper.instance().createOrgNotificationError(1, { id: 2 }); + expect(createOrganizationNotificationError).toHaveBeenCalledWith(1, { id: 2 }); + }); +}); diff --git a/src/api.js b/src/api.js index ce915bd136..d0281a09a4 100644 --- a/src/api.js +++ b/src/api.js @@ -70,6 +70,36 @@ class APIClient { return this.http.get(endpoint); } + getOrganizationNotifications (id, params = {}) { + const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates/`; + + return this.http.get(endpoint, { params }); + } + + getOrganizationNotificationSuccess (id, params = {}) { + const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates_success/`; + + return this.http.get(endpoint, { params }); + } + + getOrganizationNotificationError (id, params = {}) { + const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates_error/`; + + return this.http.get(endpoint, { params }); + } + + createOrganizationNotificationSuccess (id, data) { + const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates_success/`; + + return this.http.post(endpoint, data); + } + + createOrganizationNotificationError (id, data) { + const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates_error/`; + + return this.http.post(endpoint, data); + } + getInstanceGroups () { return this.http.get(API_INSTANCE_GROUPS); } diff --git a/src/app.scss b/src/app.scss index 5d2f72d701..d9b34ab4ad 100644 --- a/src/app.scss +++ b/src/app.scss @@ -82,6 +82,10 @@ --pf-c-data-list__item--PaddingTop: 16px; --pf-c-data-list__item--PaddingBottom: 16px; + + .pf-c-badge:not(:last-child), .pf-c-switch:not(:last-child) { + margin-right: 18px; + } } .pf-c-data-list__item { @@ -107,10 +111,6 @@ margin-right: 8px; } -.pf-c-data-list__cell span { - margin-right: 18px; -} - // // about modal overrides // @@ -158,6 +158,16 @@ border-top: 1px solid #d7d7d7; border-bottom: 1px solid #d7d7d7; } +.at-c-listCardBody { + --pf-c-card__footer--PaddingX: 0; + --pf-c-card__footer--PaddingY: 0; + --pf-c-card__body--PaddingX: 0; + --pf-c-card__body--PaddingY: 0; +} +.pf-c-data-list__item { + --pf-c-data-list__item--PaddingLeft: 20px; + --pf-c-data-list__item--PaddingRight: 20px; +} // // pf modal overrides // diff --git a/src/components/DataListToolbar/DataListToolbar.jsx b/src/components/DataListToolbar/DataListToolbar.jsx index 0936e7bdc8..8ea46641a8 100644 --- a/src/components/DataListToolbar/DataListToolbar.jsx +++ b/src/components/DataListToolbar/DataListToolbar.jsx @@ -106,7 +106,9 @@ class DataListToolbar extends React.Component { sortedColumnKey, sortOrder, addUrl, - showExpandCollapse + showExpandCollapse, + showDelete, + showSelectAll } = this.props; const { // isActionDropdownOpen, @@ -149,19 +151,19 @@ class DataListToolbar extends React.Component {
- - - - - - + + { showSelectAll && ( + + + + + + )}
@@ -248,17 +250,19 @@ class DataListToolbar extends React.Component { - - - + + + )} {addUrl && (