From 9d66b583b70024a86e412ab270b7b0c9872dfe3b Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Mon, 29 Apr 2019 10:08:50 -0400 Subject: [PATCH] 158 paginated data list (#180) * working: rename OrganizationTeamsList to PaginatedDataList * convert org notifications list fully to PaginatedDataList * update NotificationList tests * refactor org access to use PaginatedDataList * update tests for org access refactor; fix pagination & sorting * restore Add Role functionality to Org roles * fix displayed text when list of items is empty * preserve query params when navigating through pagination * fix bugs after RBAC rebase * fix lint errors, fix add org access button --- .eslintrc | 1 + .../__snapshots__/enzymeHelpers.test.jsx.snap | 6 +- .../components/NotificationList.test.jsx | 168 --- .../components/NotificationListItem.test.jsx | 46 +- .../PaginatedDataList.test.jsx} | 20 +- .../NotificationListItem.test.jsx.snap | 369 +++++ __tests__/enzymeHelpers.jsx | 4 +- .../DeleteRoleConfirmationModal.test.jsx | 27 + .../OrganizationAccessItem.test.jsx | 37 + .../DeleteRoleConfirmationModal.test.jsx.snap | 531 +++++++ .../OrganizationAccessItem.test.jsx.snap | 326 ++++ .../Organization/OrganizationAccess.test.jsx | 181 ++- .../OrganizationNotifications.test.jsx | 187 ++- .../Organization/OrganizationTeams.test.jsx | 13 +- .../OrganizationAccess.test.jsx.snap | 36 + .../OrganizationNotifications.test.jsx.snap | 1309 +++++++++++++++++ __tests__/util/qs.test.js | 20 + __tests__/util/strings.test.js | 34 + src/api.js | 10 + .../NotificationListItem.jsx | 151 +- .../NotificationsList/Notifications.list.jsx | 351 ----- .../PaginatedDataList/PaginatedDataList.jsx | 230 +++ src/components/PaginatedDataList/index.js | 3 + src/components/Search/Search.jsx | 4 +- .../DeleteRoleConfirmationModal.jsx | 80 + .../components/OrganizationAccessItem.jsx | 169 +++ .../components/OrganizationAccessList.jsx | 486 ------ .../components/OrganizationTeamsList.jsx | 173 --- .../screens/Organization/Organization.jsx | 32 +- .../Organization/OrganizationAccess.jsx | 220 ++- .../OrganizationNotifications.jsx | 230 ++- .../Organization/OrganizationTeams.jsx | 10 +- .../screens/OrganizationsList.jsx | 28 +- src/types.js | 49 + src/util/qs.js | 3 +- src/util/strings.js | 16 + 36 files changed, 4133 insertions(+), 1427 deletions(-) delete mode 100644 __tests__/components/NotificationList.test.jsx rename __tests__/{pages/Organizations/components/OrganizationTeamsList.test.jsx => components/PaginatedDataList.test.jsx} (85%) create mode 100644 __tests__/components/__snapshots__/NotificationListItem.test.jsx.snap create mode 100644 __tests__/pages/Organizations/components/DeleteRoleConfirmationModal.test.jsx create mode 100644 __tests__/pages/Organizations/components/OrganizationAccessItem.test.jsx create mode 100644 __tests__/pages/Organizations/components/__snapshots__/DeleteRoleConfirmationModal.test.jsx.snap create mode 100644 __tests__/pages/Organizations/components/__snapshots__/OrganizationAccessItem.test.jsx.snap create mode 100644 __tests__/pages/Organizations/screens/Organization/__snapshots__/OrganizationAccess.test.jsx.snap create mode 100644 __tests__/pages/Organizations/screens/Organization/__snapshots__/OrganizationNotifications.test.jsx.snap create mode 100644 __tests__/util/strings.test.js delete mode 100644 src/components/NotificationsList/Notifications.list.jsx create mode 100644 src/components/PaginatedDataList/PaginatedDataList.jsx create mode 100644 src/components/PaginatedDataList/index.js create mode 100644 src/pages/Organizations/components/DeleteRoleConfirmationModal.jsx create mode 100644 src/pages/Organizations/components/OrganizationAccessItem.jsx delete mode 100644 src/pages/Organizations/components/OrganizationAccessList.jsx delete mode 100644 src/pages/Organizations/components/OrganizationTeamsList.jsx create mode 100644 src/types.js create mode 100644 src/util/strings.js diff --git a/.eslintrc b/.eslintrc index 463676d62b..3bd83f3041 100644 --- a/.eslintrc +++ b/.eslintrc @@ -51,6 +51,7 @@ "no-unused-expressions": ["error", { "allowShortCircuit": true }], "react/prefer-stateless-function": "off", "react/prop-types": "off", + "react/sort-comp": ["error", {}], "jsx-a11y/label-has-for": "off", "jsx-a11y/label-has-associated-control": "off" } diff --git a/__tests__/__snapshots__/enzymeHelpers.test.jsx.snap b/__tests__/__snapshots__/enzymeHelpers.test.jsx.snap index 87ef1a6565..892684faf0 100644 --- a/__tests__/__snapshots__/enzymeHelpers.test.jsx.snap +++ b/__tests__/__snapshots__/enzymeHelpers.test.jsx.snap @@ -49,11 +49,7 @@ exports[`mountWithContexts injected I18nProvider should mount and render deeply exports[`mountWithContexts injected Network should mount and render 1`] = `
diff --git a/__tests__/components/NotificationList.test.jsx b/__tests__/components/NotificationList.test.jsx deleted file mode 100644 index 2261292ee0..0000000000 --- a/__tests__/components/NotificationList.test.jsx +++ /dev/null @@ -1,168 +0,0 @@ -import React from 'react'; -import { mountWithContexts } from '../enzymeHelpers'; -import Notifications, { _Notifications } from '../../src/components/NotificationsList/Notifications.list'; - -describe('', () => { - test('initially renders succesfully', () => { - mountWithContexts( - {}} - onReadNotifications={() => {}} - onReadSuccess={() => {}} - onCreateError={() => {}} - onCreateSuccess={() => {}} - canToggleNotifications - /> - ); - }); - - test('fetches notifications on mount', () => { - const spy = jest.spyOn(_Notifications.prototype, 'readNotifications'); - mountWithContexts( - {}} - onReadNotifications={() => {}} - onReadSuccess={() => {}} - onCreateError={() => {}} - onCreateSuccess={() => {}} - canToggleNotifications - /> - ); - expect(spy).toHaveBeenCalled(); - }); - - test('toggle success calls post', () => { - const spy = jest.spyOn(_Notifications.prototype, 'createSuccess'); - const wrapper = mountWithContexts( - {}} - onReadNotifications={() => {}} - onReadSuccess={() => {}} - onCreateError={() => {}} - onCreateSuccess={() => {}} - canToggleNotifications - /> - ).find('Notifications'); - wrapper.instance().toggleNotification(1, true, 'success'); - expect(spy).toHaveBeenCalledWith(1, true); - }); - - test('post success makes request and updates state properly', async () => { - const onCreateSuccess = jest.fn(); - const wrapper = mountWithContexts( - <_Notifications - match={{ path: '/organizations/:id/?tab=notifications', url: '/organizations/:id/?tab=notifications', params: { id: 1 } }} - location={{ search: '', pathname: '/organizations/:id/?tab=notifications' }} - handleHttpError={() => {}} - onReadError={() => {}} - onReadNotifications={() => {}} - onReadSuccess={() => {}} - onCreateError={() => {}} - onCreateSuccess={onCreateSuccess} - canToggleNotifications - /> - ).find('Notifications'); - wrapper.setState({ successTemplateIds: [44] }); - await wrapper.instance().createSuccess(44, true); - expect(onCreateSuccess).toHaveBeenCalledWith(1, { id: 44, disassociate: true }); - expect(wrapper.state('successTemplateIds')).not.toContain(44); - await wrapper.instance().createSuccess(44, false); - expect(onCreateSuccess).toHaveBeenCalledWith(1, { id: 44 }); - expect(wrapper.state('successTemplateIds')).toContain(44); - }); - - test('toggle error calls post', () => { - const spy = jest.spyOn(_Notifications.prototype, 'createError'); - const wrapper = mountWithContexts( - {}} - onReadNotifications={() => {}} - onReadSuccess={() => {}} - onCreateError={() => {}} - onCreateSuccess={() => {}} - canToggleNotifications - /> - ).find('Notifications'); - wrapper.instance().toggleNotification(1, true, 'error'); - expect(spy).toHaveBeenCalledWith(1, true); - }); - - test('post error makes request and updates state properly', async () => { - const onCreateError = jest.fn(); - const wrapper = mountWithContexts( - <_Notifications - match={{ path: '/organizations/:id/?tab=notifications', url: '/organizations/:id/?tab=notifications', params: { id: 1 } }} - location={{ search: '', pathname: '/organizations/:id/?tab=notifications' }} - handleHttpError={() => {}} - onReadError={() => {}} - onReadNotifications={() => {}} - onReadSuccess={() => {}} - onCreateError={onCreateError} - onCreateSuccess={() => {}} - canToggleNotifications - /> - ).find('Notifications'); - wrapper.setState({ errorTemplateIds: [44] }); - await wrapper.instance().createError(44, true); - expect(onCreateError).toHaveBeenCalledWith(1, { id: 44, disassociate: true }); - expect(wrapper.state('errorTemplateIds')).not.toContain(44); - await wrapper.instance().createError(44, false); - expect(onCreateError).toHaveBeenCalledWith(1, { id: 44 }); - expect(wrapper.state('errorTemplateIds')).toContain(44); - }); - - test('fetchNotifications', async () => { - const mockQueryParams = { - page: 44, - page_size: 10, - order_by: 'name' - }; - const onReadNotifications = jest.fn().mockResolvedValue({ - data: { - results: [ - { id: 1, notification_type: 'slack' }, - { id: 2, notification_type: 'email' }, - { id: 3, notification_type: 'github' } - ] - } - }); - const onReadSuccess = jest.fn().mockResolvedValue({ - data: { - results: [ - { id: 1 } - ] - } - }); - const onReadError = jest.fn().mockResolvedValue({ - data: { - results: [ - { id: 2 } - ] - } - }); - const wrapper = mountWithContexts( - <_Notifications - match={{ path: '/organizations/:id/?tab=notifications', url: '/organizations/:id/?tab=notifications', params: { id: 1 } }} - location={{ search: '', pathname: '/organizations/:id/?tab=notifications' }} - handleHttpError={() => {}} - onReadError={onReadError} - onReadNotifications={onReadNotifications} - onReadSuccess={onReadSuccess} - onCreateError={() => {}} - onCreateSuccess={() => {}} - canToggleNotifications - /> - ).find('Notifications'); - wrapper.instance().updateUrl = jest.fn(); - await wrapper.instance().readNotifications(mockQueryParams); - expect(onReadNotifications).toHaveBeenCalledWith(1, mockQueryParams); - expect(onReadSuccess).toHaveBeenCalledWith(1, { - id__in: '1,2,3' - }); - expect(onReadError).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 index 3ce89b72ba..d9df59c155 100644 --- a/__tests__/components/NotificationListItem.test.jsx +++ b/__tests__/components/NotificationListItem.test.jsx @@ -2,38 +2,49 @@ import React from 'react'; import { mountWithContexts } from '../enzymeHelpers'; import NotificationListItem from '../../src/components/NotificationsList/NotificationListItem'; -describe('', () => { +describe('', () => { let wrapper; - const toggleNotification = jest.fn(); + let toggleNotification; + + beforeEach(() => { + toggleNotification = jest.fn(); + }); afterEach(() => { if (wrapper) { wrapper.unmount(); wrapper = null; } + jest.clearAllMocks(); }); test('initially renders succesfully', () => { wrapper = mountWithContexts( ); - expect(wrapper.length).toBe(1); + expect(wrapper.find('NotificationListItem')).toMatchSnapshot(); }); test('handles success click when toggle is on', () => { wrapper = mountWithContexts( ); @@ -44,11 +55,14 @@ describe('', () => { test('handles success click when toggle is off', () => { wrapper = mountWithContexts( ); @@ -59,11 +73,14 @@ describe('', () => { test('handles error click when toggle is on', () => { wrapper = mountWithContexts( ); @@ -74,11 +91,14 @@ describe('', () => { test('handles error click when toggle is off', () => { wrapper = mountWithContexts( ); diff --git a/__tests__/pages/Organizations/components/OrganizationTeamsList.test.jsx b/__tests__/components/PaginatedDataList.test.jsx similarity index 85% rename from __tests__/pages/Organizations/components/OrganizationTeamsList.test.jsx rename to __tests__/components/PaginatedDataList.test.jsx index 4f0924826e..2196ae512b 100644 --- a/__tests__/pages/Organizations/components/OrganizationTeamsList.test.jsx +++ b/__tests__/components/PaginatedDataList.test.jsx @@ -1,8 +1,8 @@ import React from 'react'; import { createMemoryHistory } from 'history'; -import { mountWithContexts } from '../../../enzymeHelpers'; -import { sleep } from '../../../testUtils'; -import OrganizationTeamsList from '../../../../src/pages/Organizations/components/OrganizationTeamsList'; +import { mountWithContexts } from '../enzymeHelpers'; +import { sleep } from '../testUtils'; +import PaginatedDataList from '../../src/components/PaginatedDataList'; const mockData = [ { id: 1, name: 'one', url: '/org/team/1' }, @@ -12,15 +12,15 @@ const mockData = [ { id: 5, name: 'five', url: '/org/team/5' }, ]; -describe('', () => { +describe('', () => { afterEach(() => { jest.restoreAllMocks(); }); test('initially renders succesfully', () => { mountWithContexts( - ', () => { initialEntries: ['/organizations/1/teams'], }); const wrapper = mountWithContexts( - ', () => { initialEntries: ['/organizations/1/teams'], }); const wrapper = mountWithContexts( - initially renders succesfully 1`] = ` + + + +
  • + +
    + + + + Foo + + + + + + slack + + +
    +
    + +
    + + + + + + +
    +
    +
  • +
    +
    +
    +`; + +exports[` initially renders succesfully 1`] = ` + + + +
  • + +
    + + + + Foo + + + + + + slack + + +
    +
    + +
    + + + + + + +
    +
    +
  • +
    +
    +
    +`; diff --git a/__tests__/enzymeHelpers.jsx b/__tests__/enzymeHelpers.jsx index b49dcb17f7..9be34dd7ae 100644 --- a/__tests__/enzymeHelpers.jsx +++ b/__tests__/enzymeHelpers.jsx @@ -50,7 +50,8 @@ const defaultContexts = { pathname: '', search: '', state: '', - } + }, + toJSON: () => '/history/', }, route: { location: { @@ -71,6 +72,7 @@ const defaultContexts = { network: { api: { getConfig: () => {}, + toJSON: () => '/api/', }, handleHttpError: () => {}, }, diff --git a/__tests__/pages/Organizations/components/DeleteRoleConfirmationModal.test.jsx b/__tests__/pages/Organizations/components/DeleteRoleConfirmationModal.test.jsx new file mode 100644 index 0000000000..e6231e13fe --- /dev/null +++ b/__tests__/pages/Organizations/components/DeleteRoleConfirmationModal.test.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { mountWithContexts } from '../../../enzymeHelpers'; +import DeleteRoleConfirmationModal from '../../../../src/pages/Organizations/components/DeleteRoleConfirmationModal'; + +const role = { + id: 3, + name: 'Member', + resource_name: 'Org', + resource_type: 'organization', + team_id: 5, + team_name: 'The Team', +}; + +describe('', () => { + test('should render initially', () => { + const wrapper = mountWithContexts( + {}} + onConfirm={() => {}} + /> + ); + wrapper.update(); + expect(wrapper.find('DeleteRoleConfirmationModal')).toMatchSnapshot(); + }); +}); diff --git a/__tests__/pages/Organizations/components/OrganizationAccessItem.test.jsx b/__tests__/pages/Organizations/components/OrganizationAccessItem.test.jsx new file mode 100644 index 0000000000..f3e9031f6b --- /dev/null +++ b/__tests__/pages/Organizations/components/OrganizationAccessItem.test.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { mountWithContexts } from '../../../enzymeHelpers'; +import OrganizationAccessItem from '../../../../src/pages/Organizations/components/OrganizationAccessItem'; + +const accessRecord = { + id: 2, + username: 'jane', + url: '/bar', + first_name: 'jane', + last_name: 'brown', + summary_fields: { + direct_access: [{ + role: { + id: 3, + name: 'Member', + resource_name: 'Org', + resource_type: 'organization', + team_id: 5, + team_name: 'The Team', + user_capabilities: { unattach: true }, + } + }], + indirect_access: [], + } +}; + +describe('', () => { + test('initially renders succesfully', () => { + const wrapper = mountWithContexts( + {}} + /> + ); + expect(wrapper.find('OrganizationAccessItem')).toMatchSnapshot(); + }); +}); diff --git a/__tests__/pages/Organizations/components/__snapshots__/DeleteRoleConfirmationModal.test.jsx.snap b/__tests__/pages/Organizations/components/__snapshots__/DeleteRoleConfirmationModal.test.jsx.snap new file mode 100644 index 0000000000..da12524981 --- /dev/null +++ b/__tests__/pages/Organizations/components/__snapshots__/DeleteRoleConfirmationModal.test.jsx.snap @@ -0,0 +1,531 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render initially 1`] = ` + + + <_default + actions={ + Array [ + , + , + ] + } + isOpen={true} + title="Remove Team Access" + variant="danger" + > + + Delete + , + , + ] + } + ariaDescribedById="" + className="awx-c-modal at-c-alertModal at-c-alertModal--danger" + hideTitle={false} + isLarge={false} + isOpen={true} + isSmall={false} + onClose={[Function]} + title="Remove Team Access" + width={null} + > + +
    +
    +
    + +
    +
    +
    +
    + } + > + + Delete + , + , + ] + } + ariaDescribedById="" + className="awx-c-modal at-c-alertModal at-c-alertModal--danger" + hideTitle={false} + id="pf-modal-0" + isLarge={false} + isOpen={true} + isSmall={false} + onClose={[Function]} + title="Remove Team Access" + width={null} + > + +
    + +
    + +
    + +
    + + + + + + + <h3 + className="pf-c-title pf-m-2xl" + > + + Remove Team Access + + </h3> + + + +
    + , + , +
    , +
    , + , + , + ] + } + id="Are you sure you want to remove<0> {0} access from<1> {1}? Doing so affects all members of the team.<2/><3/>If you<4><5> only want to remove access for this particular user, please remove them from the team." + values={ + Object { + "0": "Member", + "1": "The Team", + } + } + > + + , + , +
    , +
    , + , + , + ] + } + i18n={"/i18n/"} + id="Are you sure you want to remove<0> {0} access from<1> {1}? Doing so affects all members of the team.<2/><3/>If you<4><5> only want to remove access for this particular user, please remove them from the team." + values={ + Object { + "0": "Member", + "1": "The Team", + } + } + > + +
    +
    +
    + + + + + +
    +
    + +
    + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + +`; diff --git a/__tests__/pages/Organizations/components/__snapshots__/OrganizationAccessItem.test.jsx.snap b/__tests__/pages/Organizations/components/__snapshots__/OrganizationAccessItem.test.jsx.snap new file mode 100644 index 0000000000..ba43a27c8e --- /dev/null +++ b/__tests__/pages/Organizations/components/__snapshots__/OrganizationAccessItem.test.jsx.snap @@ -0,0 +1,326 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` initially renders succesfully 1`] = ` + + + +
  • + +
    + + + + + +
    + +
    + Name +
    +
    + +

    + jane brown +

    +
    +
    +
    +
    +
    +
    + +
    +
      + +
      + Team Roles +
      +
      + + +
    • + + Member + + + + + +
    • +
      +
      +
    +
    +
    +
  • +
    +
    +
    +`; diff --git a/__tests__/pages/Organizations/screens/Organization/OrganizationAccess.test.jsx b/__tests__/pages/Organizations/screens/Organization/OrganizationAccess.test.jsx index a998402345..0c81e9fc55 100644 --- a/__tests__/pages/Organizations/screens/Organization/OrganizationAccess.test.jsx +++ b/__tests__/pages/Organizations/screens/Organization/OrganizationAccess.test.jsx @@ -1,34 +1,173 @@ import React from 'react'; import { mountWithContexts } from '../../../../enzymeHelpers'; import OrganizationAccess from '../../../../../src/pages/Organizations/screens/Organization/OrganizationAccess'; +import { sleep } from '../../../../testUtils'; describe('', () => { + let network; const organization = { id: 1, name: 'Default' }; - test('initially renders succesfully', () => { - mountWithContexts(); + + const data = { + count: 2, + results: [{ + id: 1, + username: 'joe', + url: '/foo', + first_name: 'joe', + last_name: 'smith', + summary_fields: { + direct_access: [{ + role: { + id: 1, + name: 'Member', + resource_name: 'Org', + resource_type: 'organization', + user_capabilities: { unattach: true }, + } + }], + indirect_access: [], + } + }, { + id: 2, + username: 'jane', + url: '/bar', + first_name: 'jane', + last_name: 'brown', + summary_fields: { + direct_access: [{ + role: { + id: 3, + name: 'Member', + resource_name: 'Org', + resource_type: 'organization', + team_id: 5, + team_name: 'The Team', + user_capabilities: { unattach: true }, + } + }], + indirect_access: [], + } + }] + }; + + beforeEach(() => { + network = { + api: { + getOrganizationAccessList: jest.fn() + .mockReturnValue(Promise.resolve({ data })), + disassociateTeamRole: jest.fn(), + disassociateUserRole: jest.fn(), + toJSON: () => '/api/', + }, + }; }); - test('passed methods as props are called appropriately', async () => { - const mockAPIAccessList = { - foo: 'bar', - }; - const mockResponse = { - status: 'success', - }; - const wrapper = mountWithContexts(, - { context: { network: { - api: { - getOrganizationAccessList: () => Promise.resolve(mockAPIAccessList), - disassociate: () => Promise.resolve(mockResponse) - }, - handleHttpError: () => {} - } } }).find('OrganizationAccess'); - const accessList = await wrapper.instance().getOrgAccessList(); - expect(accessList).toEqual(mockAPIAccessList); - const resp = await wrapper.instance().removeRole(2, 3, 'users'); - expect(resp).toEqual(mockResponse); + test.only('initially renders succesfully', () => { + const wrapper = mountWithContexts( + , + { context: { network } } + ); + expect(wrapper.find('OrganizationAccess')).toMatchSnapshot(); + }); + + test('should fetch and display access records on mount', async () => { + const wrapper = mountWithContexts( + , + { context: { network } } + ); + await sleep(0); + wrapper.update(); + expect(network.api.getOrganizationAccessList).toHaveBeenCalled(); + expect(wrapper.find('OrganizationAccess').state('isInitialized')).toBe(true); + expect(wrapper.find('PaginatedDataList').prop('items')).toEqual(data.results); + expect(wrapper.find('OrganizationAccessItem')).toHaveLength(2); + }); + + test('should open confirmation dialog when deleting role', async () => { + const wrapper = mountWithContexts( + , + { context: { network } } + ); + await sleep(0); + wrapper.update(); + + const button = wrapper.find('ChipButton').at(0); + button.prop('onClick')(); + wrapper.update(); + + const component = wrapper.find('OrganizationAccess'); + expect(component.state('roleToDelete')) + .toEqual(data.results[0].summary_fields.direct_access[0].role); + expect(component.state('roleToDeleteAccessRecord')) + .toEqual(data.results[0]); + expect(component.find('DeleteRoleConfirmationModal')).toHaveLength(1); + }); + + it('should close dialog when cancel button clicked', async () => { + const wrapper = mountWithContexts( + , + { context: { network } } + ); + await sleep(0); + wrapper.update(); + const button = wrapper.find('ChipButton').at(0); + button.prop('onClick')(); + wrapper.update(); + + wrapper.find('DeleteRoleConfirmationModal').prop('onCancel')(); + const component = wrapper.find('OrganizationAccess'); + expect(component.state('roleToDelete')).toBeNull(); + expect(component.state('roleToDeleteAccessRecord')).toBeNull(); + expect(network.api.disassociateTeamRole).not.toHaveBeenCalled(); + expect(network.api.disassociateUserRole).not.toHaveBeenCalled(); + }); + + it('should delete user role', async () => { + const wrapper = mountWithContexts( + , + { context: { network } } + ); + await sleep(0); + wrapper.update(); + const button = wrapper.find('ChipButton').at(0); + button.prop('onClick')(); + wrapper.update(); + + wrapper.find('DeleteRoleConfirmationModal').prop('onConfirm')(); + await sleep(0); + wrapper.update(); + + const component = wrapper.find('OrganizationAccess'); + expect(component.state('roleToDelete')).toBeNull(); + expect(component.state('roleToDeleteAccessRecord')).toBeNull(); + expect(network.api.disassociateTeamRole).not.toHaveBeenCalled(); + expect(network.api.disassociateUserRole).toHaveBeenCalledWith(1, 1); + expect(network.api.getOrganizationAccessList).toHaveBeenCalledTimes(2); + }); + + it('should delete team role', async () => { + const wrapper = mountWithContexts( + , + { context: { network } } + ); + await sleep(0); + wrapper.update(); + const button = wrapper.find('ChipButton').at(1); + button.prop('onClick')(); + wrapper.update(); + + wrapper.find('DeleteRoleConfirmationModal').prop('onConfirm')(); + await sleep(0); + wrapper.update(); + + const component = wrapper.find('OrganizationAccess'); + expect(component.state('roleToDelete')).toBeNull(); + expect(component.state('roleToDeleteAccessRecord')).toBeNull(); + expect(network.api.disassociateTeamRole).toHaveBeenCalledWith(5, 3); + expect(network.api.disassociateUserRole).not.toHaveBeenCalled(); + expect(network.api.getOrganizationAccessList).toHaveBeenCalledTimes(2); }); }); diff --git a/__tests__/pages/Organizations/screens/Organization/OrganizationNotifications.test.jsx b/__tests__/pages/Organizations/screens/Organization/OrganizationNotifications.test.jsx index 5b10cda0c9..766f6c2b6e 100644 --- a/__tests__/pages/Organizations/screens/Organization/OrganizationNotifications.test.jsx +++ b/__tests__/pages/Organizations/screens/Organization/OrganizationNotifications.test.jsx @@ -1,45 +1,170 @@ import React from 'react'; import { mountWithContexts } from '../../../../enzymeHelpers'; - import OrganizationNotifications from '../../../../../src/pages/Organizations/screens/Organization/OrganizationNotifications'; +import { sleep } from '../../../../testUtils'; describe('', () => { - let api; + let data; + let network; beforeEach(() => { - api = { - getOrganizationNotifications: jest.fn(), - getOrganizationNotificationSuccess: jest.fn(), - getOrganizationNotificationError: jest.fn(), - createOrganizationNotificationSuccess: jest.fn(), - createOrganizationNotificationError: jest.fn() + data = { + count: 2, + results: [{ + id: 1, + name: 'Notification one', + url: '/api/v2/notification_templates/1/', + notification_type: 'email', + }, { + id: 2, + name: 'Notification two', + url: '/api/v2/notification_templates/2/', + notification_type: 'email', + }] + }; + network = { + api: { + getOrganizationNotifications: jest.fn() + .mockReturnValue(Promise.resolve({ data })), + getOrganizationNotificationSuccess: jest.fn() + .mockReturnValue(Promise.resolve({ + data: { results: [{ id: 1 }] }, + })), + getOrganizationNotificationError: jest.fn() + .mockReturnValue(Promise.resolve({ + data: { results: [{ id: 2 }] }, + })), + createOrganizationNotificationSuccess: jest.fn(), + createOrganizationNotificationError: jest.fn(), + toJSON: () => '/api/', + } }; }); - test('initially renders succesfully', () => { - mountWithContexts( - , { context: { network: { - api, - handleHttpError: () => {} - } } } - ); + afterEach(() => { + jest.clearAllMocks(); }); - test('handles api requests', () => { + + test('initially renders succesfully', async () => { const wrapper = mountWithContexts( - , { context: { network: { - api, - handleHttpError: () => {} - } } } - ).find('OrganizationNotifications'); - wrapper.instance().readOrgNotifications(1, { foo: 'bar' }); - expect(api.getOrganizationNotifications).toHaveBeenCalledWith(1, { foo: 'bar' }); - wrapper.instance().readOrgNotificationSuccess(1, { foo: 'bar' }); - expect(api.getOrganizationNotificationSuccess).toHaveBeenCalledWith(1, { foo: 'bar' }); - wrapper.instance().readOrgNotificationError(1, { foo: 'bar' }); - expect(api.getOrganizationNotificationError).toHaveBeenCalledWith(1, { foo: 'bar' }); - wrapper.instance().createOrgNotificationSuccess(1, { id: 2 }); - expect(api.createOrganizationNotificationSuccess).toHaveBeenCalledWith(1, { id: 2 }); - wrapper.instance().createOrgNotificationError(1, { id: 2 }); - expect(api.createOrganizationNotificationError).toHaveBeenCalledWith(1, { id: 2 }); + , + { context: { network } } + ); + await sleep(0); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + }); + + test('should render list fetched of items', async () => { + const wrapper = mountWithContexts( + , + { + context: { network } + } + ); + await sleep(0); + wrapper.update(); + + expect(network.api.getOrganizationNotifications).toHaveBeenCalled(); + expect(wrapper.find('OrganizationNotifications').state('notifications')) + .toEqual(data.results); + const items = wrapper.find('NotificationListItem'); + expect(items).toHaveLength(2); + expect(items.at(0).prop('successTurnedOn')).toEqual(true); + expect(items.at(0).prop('errorTurnedOn')).toEqual(false); + expect(items.at(1).prop('successTurnedOn')).toEqual(false); + expect(items.at(1).prop('errorTurnedOn')).toEqual(true); + }); + + test('should enable success notification', async () => { + const wrapper = mountWithContexts( + , + { + context: { network } + } + ); + await sleep(0); + wrapper.update(); + + expect( + wrapper.find('OrganizationNotifications').state('successTemplateIds') + ).toEqual([1]); + const items = wrapper.find('NotificationListItem'); + items.at(1).find('Switch').at(0).prop('onChange')(); + expect(network.api.createOrganizationNotificationSuccess).toHaveBeenCalled(); + await sleep(0); + wrapper.update(); + expect( + wrapper.find('OrganizationNotifications').state('successTemplateIds') + ).toEqual([1, 2]); + }); + + test('should enable error notification', async () => { + const wrapper = mountWithContexts( + , + { + context: { network } + } + ); + await sleep(0); + wrapper.update(); + + expect( + wrapper.find('OrganizationNotifications').state('errorTemplateIds') + ).toEqual([2]); + const items = wrapper.find('NotificationListItem'); + items.at(0).find('Switch').at(1).prop('onChange')(); + expect(network.api.createOrganizationNotificationError).toHaveBeenCalled(); + await sleep(0); + wrapper.update(); + expect( + wrapper.find('OrganizationNotifications').state('errorTemplateIds') + ).toEqual([2, 1]); + }); + + test('should disable success notification', async () => { + const wrapper = mountWithContexts( + , + { + context: { network } + } + ); + await sleep(0); + wrapper.update(); + + expect( + wrapper.find('OrganizationNotifications').state('successTemplateIds') + ).toEqual([1]); + const items = wrapper.find('NotificationListItem'); + items.at(0).find('Switch').at(0).prop('onChange')(); + expect(network.api.createOrganizationNotificationSuccess).toHaveBeenCalled(); + await sleep(0); + wrapper.update(); + expect( + wrapper.find('OrganizationNotifications').state('successTemplateIds') + ).toEqual([]); + }); + + test('should disable error notification', async () => { + const wrapper = mountWithContexts( + , + { + context: { network } + } + ); + await sleep(0); + wrapper.update(); + + expect( + wrapper.find('OrganizationNotifications').state('errorTemplateIds') + ).toEqual([2]); + const items = wrapper.find('NotificationListItem'); + items.at(1).find('Switch').at(1).prop('onChange')(); + expect(network.api.createOrganizationNotificationError).toHaveBeenCalled(); + await sleep(0); + wrapper.update(); + expect( + wrapper.find('OrganizationNotifications').state('errorTemplateIds') + ).toEqual([]); }); }); diff --git a/__tests__/pages/Organizations/screens/Organization/OrganizationTeams.test.jsx b/__tests__/pages/Organizations/screens/Organization/OrganizationTeams.test.jsx index 76ab90af11..b418f5ed0c 100644 --- a/__tests__/pages/Organizations/screens/Organization/OrganizationTeams.test.jsx +++ b/__tests__/pages/Organizations/screens/Organization/OrganizationTeams.test.jsx @@ -5,7 +5,6 @@ import { createMemoryHistory } from 'history'; import { mountWithContexts } from '../../../../enzymeHelpers'; import { sleep } from '../../../../testUtils'; import OrganizationTeams, { _OrganizationTeams } from '../../../../../src/pages/Organizations/screens/Organization/OrganizationTeams'; -import OrganizationTeamsList from '../../../../../src/pages/Organizations/components/OrganizationTeamsList'; const listData = { data: { @@ -52,7 +51,7 @@ describe('', () => { }); }); - test('should pass fetched teams to list component', async () => { + test('should pass fetched teams to PaginatedDatalist', async () => { const readOrganizationTeamsList = jest.fn(() => Promise.resolve(listData)); const wrapper = mountWithContexts( ', () => { await sleep(0); wrapper.update(); - const list = wrapper.find('OrganizationTeamsList'); - expect(list.prop('teams')).toEqual(listData.data.results); + const list = wrapper.find('PaginatedDataList'); + expect(list.prop('items')).toEqual(listData.data.results); expect(list.prop('itemCount')).toEqual(listData.data.count); expect(list.prop('queryParams')).toEqual({ page: 1, @@ -76,7 +75,7 @@ describe('', () => { }); }); - test('should pass queryParams to OrganizationTeamsList', async () => { + test('should pass queryParams to PaginatedDataList', async () => { const page1Data = listData; const page2Data = { data: { @@ -111,7 +110,7 @@ describe('', () => { await sleep(0); wrapper.update(); - const list = wrapper.find(OrganizationTeamsList); + const list = wrapper.find('PaginatedDataList'); expect(list.prop('queryParams')).toEqual({ page: 1, page_size: 5, @@ -123,7 +122,7 @@ describe('', () => { await sleep(0); wrapper.update(); - const list2 = wrapper.find(OrganizationTeamsList); + const list2 = wrapper.find('PaginatedDataList'); expect(list2.prop('queryParams')).toEqual({ page: 2, page_size: 5, diff --git a/__tests__/pages/Organizations/screens/Organization/__snapshots__/OrganizationAccess.test.jsx.snap b/__tests__/pages/Organizations/screens/Organization/__snapshots__/OrganizationAccess.test.jsx.snap new file mode 100644 index 0000000000..3f2019669a --- /dev/null +++ b/__tests__/pages/Organizations/screens/Organization/__snapshots__/OrganizationAccess.test.jsx.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` initially renders succesfully 1`] = ` + +
    + Loading... +
    +
    +`; diff --git a/__tests__/pages/Organizations/screens/Organization/__snapshots__/OrganizationNotifications.test.jsx.snap b/__tests__/pages/Organizations/screens/Organization/__snapshots__/OrganizationNotifications.test.jsx.snap new file mode 100644 index 0000000000..75fd347ebc --- /dev/null +++ b/__tests__/pages/Organizations/screens/Organization/__snapshots__/OrganizationNotifications.test.jsx.snap @@ -0,0 +1,1309 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` initially renders succesfully 1`] = ` + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    + +
    + + +
    + + Name + + } + > +
    + + +
    + } + > + + +
    + } + > + + + +
    + + + + +