From 33f7bf67e142b08c84b19b379088c6f97b83c861 Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Mon, 29 Apr 2019 17:00:04 -0400 Subject: [PATCH 1/4] fix merge conflicts --- src/pages/Organizations/screens/OrganizationsList.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/Organizations/screens/OrganizationsList.jsx b/src/pages/Organizations/screens/OrganizationsList.jsx index b3eb0106c0..e8839b5079 100644 --- a/src/pages/Organizations/screens/OrganizationsList.jsx +++ b/src/pages/Organizations/screens/OrganizationsList.jsx @@ -62,8 +62,7 @@ class OrganizationsList extends Component { loading: true, results: [], selected: [], - isModalOpen: false - + isModalOpen: false, }; this.onSearch = this.onSearch.bind(this); @@ -77,7 +76,7 @@ class OrganizationsList extends Component { this.fetchOrganizations = this.fetchOrganizations.bind(this); this.handleOrgDelete = this.handleOrgDelete.bind(this); this.handleOpenOrgDeleteModal = this.handleOpenOrgDeleteModal.bind(this); - this.handleCloseOrgDeleteModal = this.handleCloseOrgDeleteModal.bind(this); + this.handleClearOrgDeleteModal = this.handleClearOrgDeleteModal.bind(this); } componentDidMount () { @@ -144,9 +143,10 @@ class OrganizationsList extends Component { return Object.assign({}, DEFAULT_PARAMS, searchParams, overrides); } - handleCloseOrgDeleteModal () { + handleClearOrgDeleteModal () { this.setState({ - isModalOpen: false + isModalOpen: false, + selected: [] }); } From 1bae944b85bf44e155f78a01ea50a46451506644 Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Mon, 29 Apr 2019 17:29:40 -0400 Subject: [PATCH 2/4] fix tests and function name changes --- .../screens/OrganizationsList.test.jsx | 264 ++++++++++++------ .../screens/OrganizationsList.jsx | 4 +- 2 files changed, 185 insertions(+), 83 deletions(-) diff --git a/__tests__/pages/Organizations/screens/OrganizationsList.test.jsx b/__tests__/pages/Organizations/screens/OrganizationsList.test.jsx index a3c737c126..7c86c44b06 100644 --- a/__tests__/pages/Organizations/screens/OrganizationsList.test.jsx +++ b/__tests__/pages/Organizations/screens/OrganizationsList.test.jsx @@ -1,8 +1,7 @@ import React from 'react'; -import { mount } from 'enzyme'; -import { MemoryRouter } from 'react-router-dom'; -import { I18nProvider } from '@lingui/react'; -import { _OrganizationsList } from '../../../../src/pages/Organizations/screens/OrganizationsList'; +import { createMemoryHistory } from 'history'; +import { mountWithContexts } from '../../../enzymeHelpers'; +import OrganizationsList, { _OrganizationsList } from '../../../../src/pages/Organizations/screens/OrganizationsList'; const mockAPIOrgsList = { data: { @@ -13,26 +12,35 @@ const mockAPIOrgsList = { related_field_counts: { teams: 3, users: 4 + }, + user_capabilities: { + delete: true } }, }, { name: 'Organization 1', - id: 1, + id: 2, summary_fields: { related_field_counts: { teams: 2, users: 5 + }, + user_capabilities: { + delete: true } }, }, { name: 'Organization 2', - id: 2, + id: 3, summary_fields: { related_field_counts: { teams: 5, users: 6 + }, + user_capabilities: { + delete: true } }, }] @@ -40,99 +48,193 @@ const mockAPIOrgsList = { isModalOpen: false, warningTitle: 'title', warningMsg: 'message' - }; -describe('', () => { +describe('<_OrganizationsList />', () => { + let wrapper; + let api; + + beforeEach(() => { + api = { + getOrganizations: jest.fn(), + destroyOrganization: jest.fn(), + }; + }); + test('initially renders succesfully', () => { - mount( - - - <_OrganizationsList - match={{ path: '/organizations', url: '/organizations' }} - location={{ search: '', pathname: '/organizations' }} - handleHttpError={() => {}} - /> - - + mountWithContexts( + ); }); - // TODO: these tests were not correct. will work to clean these tests up after PR - test.skip('Modal closes when close button is clicked.', async (done) => { - const handleClearOrgsToDelete = jest.fn(); - const wrapper = mount( - - - <_OrganizationsList - match={{ path: '/organizations', url: '/organizations' }} - location={{ search: '', pathname: '/organizations' }} - getItems={({ data: { orgsToDelete: [{ name: 'Organization 1', id: 1 }] } })} - handleClearOrgsToDelete={handleClearOrgsToDelete()} - handleHttpError={() => {}} - /> - - + test('Puts 1 selected Org in state when onSelect is called.', async () => { + wrapper = mountWithContexts( + + ).find('OrganizationsList'); + await setImmediate(async () => { + wrapper.setState({ + results: mockAPIOrgsList.data.results + }); + wrapper.update(); + }); + wrapper.instance().onSelect(mockAPIOrgsList.data.results.slice(0, 1)); + expect(wrapper.state('selected').length).toBe(1); + }); + + test('Puts all Orgs in state when onSelectAll is called.', async () => { + wrapper = mountWithContexts( + + ).find('OrganizationsList'); + wrapper.setState( + mockAPIOrgsList.data ); wrapper.find({ type: 'checkbox' }).simulate('click'); + wrapper.instance().onSelectAll(true); + expect(wrapper.find('OrganizationsList').state().selected.length).toEqual(wrapper.state().results.length); + }); + test('orgsToDelete is 0 when close modal button is clicked.', async () => { + wrapper = mountWithContexts( + + ); + wrapper.find('OrganizationsList').setState({ + results: mockAPIOrgsList.data.results, + isModalOpen: mockAPIOrgsList.isModalOpen, + selected: mockAPIOrgsList.data.results + }); + const component = wrapper.find('OrganizationsList'); wrapper.find('DataListToolbar').prop('onOpenDeleteModal')(); - expect(wrapper.find('OrganizationsList').state().isModalOpen).toEqual(true); - setImmediate(() => { - wrapper.update(); - wrapper.setState({ - selected: mockAPIOrgsList.data.results.map((result) => result.id), - orgsToDelete: mockAPIOrgsList.data.results.map((result) => result), - isModalOpen: true, - }); + wrapper.update(); + const button = wrapper.find('ModalBoxCloseButton'); + button.prop('onClose')(); + wrapper.update(); + expect(component.state('isModalOpen')).toBe(false); + expect(component.state('selected').length).toBe(0); + wrapper.unmount(); + }); - wrapper.find('button[aria-label="Close"]').simulate('click'); - expect(handleClearOrgsToDelete).toBeCalled(); - const list = wrapper.find('OrganizationsList'); - expect(list.state().isModalOpen).toBe(false); - done(); + test('orgsToDelete is 0 when cancel modal button is clicked.', async () => { + wrapper = mountWithContexts( + + ); + wrapper.find('OrganizationsList').setState({ + results: mockAPIOrgsList.data.results, + isModalOpen: mockAPIOrgsList.isModalOpen, + selected: mockAPIOrgsList.data.results + }); + const component = wrapper.find('OrganizationsList'); + wrapper.find('DataListToolbar').prop('onOpenDeleteModal')(); + wrapper.update(); + const button = wrapper.find('ModalBoxFooter').find('button').at(1); + button.prop('onClick')(); + wrapper.update(); + expect(component.state('isModalOpen')).toBe(false); + expect(component.state('selected').length).toBe(0); + wrapper.unmount(); + }); + + test('api is called to delete Orgs for each org in orgsToDelete.', async () => { + const fetchOrganizations = jest.fn(() => wrapper.find('OrganizationsList').setState({ + results: [] + })); + wrapper = mountWithContexts( + , { + context: { network: { api } } + } + ); + const component = wrapper.find('OrganizationsList'); + wrapper.find('OrganizationsList').setState({ + results: mockAPIOrgsList.data.results, + isModalOpen: mockAPIOrgsList.isModalOpen, + selected: mockAPIOrgsList.data.results + }); + wrapper.find('DataListToolbar').prop('onOpenDeleteModal')(); + wrapper.update(); + const button = wrapper.find('ModalBoxFooter').find('button').at(0); + button.simulate('click'); + wrapper.update(); + expect(api.destroyOrganization).toHaveBeenCalledTimes(component.state('results').length); + }); + + test('call fetchOrganizations after org(s) have been deleted', async () => { + const fetchOrgs = jest.spyOn(_OrganizationsList.prototype, 'fetchOrganizations'); + const event = { preventDefault: () => { } }; + wrapper = mountWithContexts( + , { + context: { network: { api } } + } + ); + wrapper.find('OrganizationsList').setState({ + results: mockAPIOrgsList.data.results, + selected: mockAPIOrgsList.data.results.slice(0, 1) + }); + const component = wrapper.find('OrganizationsList'); + await component.instance().handleOrgDelete(event); + expect(fetchOrgs).toBeCalled(); + }); + + test('url updates properly', async () => { + const history = createMemoryHistory({ + initialEntries: ['organizations?order_by=name&page=1&page_size=5'], + }); + wrapper = mountWithContexts( + , { + context: { router: { history } } + } + ); + const component = wrapper.find('OrganizationsList'); + component.instance().updateUrl({ + page: 1, + page_size: 5, + order_by: 'modified' + }); + expect(history.location.search).toBe('?order_by=modified&page=1&page_size=5'); + }); + + test('onSort sends the correct information to fetchOrganizations', async () => { + const history = createMemoryHistory({ + initialEntries: ['organizations?order_by=name&page=1&page_size=5'], + }); + const fetchOrganizations = jest.spyOn(_OrganizationsList.prototype, 'fetchOrganizations'); + wrapper = mountWithContexts( + , { + context: { + router: { history } + } + } + ); + const component = wrapper.find('OrganizationsList'); + component.instance().onSort('modified', 'ascending'); + expect(fetchOrganizations).toBeCalledWith({ + page: 1, + page_size: 5, + order_by: 'modified' }); }); - // TODO: these tests were not correct. will work to clean these tests up after PR - test.skip('Orgs to delete length is 0 when all orgs are selected and Delete button is called.', async (done) => { - const handleClearOrgsToDelete = jest.fn(); - const handleOrgDelete = jest.fn(); - const fetchOrganizations = jest.fn(); - const wrapper = mount( - - - <_OrganizationsList - match={{ path: '/organizations', url: '/organizations' }} - location={{ search: '', pathname: '/organizations' }} - getItems={({ data: { orgsToDelete: [{ name: 'Organization 1', id: 1 }] } })} - handleClearOrgsToDelete={handleClearOrgsToDelete()} - handleOrgDelete={handleOrgDelete()} - fetchOrganizations={fetchOrganizations()} - handleHttpError={() => {}} - /> - - + test('error is thrown when org not successfully deleted from api', async () => { + const history = createMemoryHistory({ + initialEntries: ['organizations?order_by=name&page=1&page_size=5'], + }); + const handleError = jest.fn(); + wrapper = mountWithContexts( + , { + context: { + router: { history }, network: { api, handleHttpError: handleError } + } + } ); - wrapper.find({ type: 'checkbox' }).simulate('click'); - wrapper.find('button[aria-label="Delete"]').simulate('click'); - - wrapper.find('DataListToolbar').prop('onOpenDeleteModal')(); - expect(wrapper.find('OrganizationsList').state().isModalOpen).toEqual(true); - setImmediate(() => { - wrapper.update(); + await setImmediate(async () => { wrapper.setState({ - selected: mockAPIOrgsList.data.results.map((result) => result.id), - orgsToDelete: mockAPIOrgsList.data.results.map((result) => result), - isModalOpen: true, + results: mockAPIOrgsList.data.results, + selected: [...mockAPIOrgsList.data.results].push({ id: 'a' }) }); wrapper.update(); - - const list = wrapper.find('OrganizationsList'); - wrapper.find('button[aria-label="confirm-delete"]').simulate('click'); - expect(list.state().orgsToDelete.length).toEqual(list.state().orgsDeleted.length); - expect(fetchOrganizations).toHaveBeenCalled(); - done(); }); + const component = wrapper.find('OrganizationsList'); + component.instance().handleOrgDelete(); + expect(handleError).toBeCalled(); }); }); diff --git a/src/pages/Organizations/screens/OrganizationsList.jsx b/src/pages/Organizations/screens/OrganizationsList.jsx index e8839b5079..fba7a5b790 100644 --- a/src/pages/Organizations/screens/OrganizationsList.jsx +++ b/src/pages/Organizations/screens/OrganizationsList.jsx @@ -297,10 +297,10 @@ class OrganizationsList extends Component { variant="danger" title={warningTitle} isOpen={isModalOpen} - onClose={this.handleCloseOrgDeleteModal} + onClose={this.handleClearOrgDeleteModal} actions={[ , - + ]} > {warningMsg} From f4da620c4d8f51068d64ab7ad8c96125977e771b Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Tue, 30 Apr 2019 09:48:42 -0400 Subject: [PATCH 3/4] updating PR --- .../screens/OrganizationsList.test.jsx | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/__tests__/pages/Organizations/screens/OrganizationsList.test.jsx b/__tests__/pages/Organizations/screens/OrganizationsList.test.jsx index 7c86c44b06..410a521aa6 100644 --- a/__tests__/pages/Organizations/screens/OrganizationsList.test.jsx +++ b/__tests__/pages/Organizations/screens/OrganizationsList.test.jsx @@ -67,21 +67,20 @@ describe('<_OrganizationsList />', () => { ); }); - test('Puts 1 selected Org in state when onSelect is called.', async () => { + test('Puts 1 selected Org in state when onSelect is called.', () => { wrapper = mountWithContexts( ).find('OrganizationsList'); - await setImmediate(async () => { - wrapper.setState({ - results: mockAPIOrgsList.data.results - }); - wrapper.update(); + + wrapper.setState({ + results: mockAPIOrgsList.data.results }); + wrapper.update(); wrapper.instance().onSelect(mockAPIOrgsList.data.results.slice(0, 1)); expect(wrapper.state('selected').length).toBe(1); }); - test('Puts all Orgs in state when onSelectAll is called.', async () => { + test('Puts all Orgs in state when onSelectAll is called.', () => { wrapper = mountWithContexts( ).find('OrganizationsList'); @@ -93,7 +92,7 @@ describe('<_OrganizationsList />', () => { expect(wrapper.find('OrganizationsList').state().selected.length).toEqual(wrapper.state().results.length); }); - test('orgsToDelete is 0 when close modal button is clicked.', async () => { + test('orgsToDelete is 0 when close modal button is clicked.', () => { wrapper = mountWithContexts( ); @@ -113,7 +112,7 @@ describe('<_OrganizationsList />', () => { wrapper.unmount(); }); - test('orgsToDelete is 0 when cancel modal button is clicked.', async () => { + test('orgsToDelete is 0 when cancel modal button is clicked.', () => { wrapper = mountWithContexts( ); @@ -133,7 +132,7 @@ describe('<_OrganizationsList />', () => { wrapper.unmount(); }); - test('api is called to delete Orgs for each org in orgsToDelete.', async () => { + test('api is called to delete Orgs for each org in orgsToDelete.', () => { const fetchOrganizations = jest.fn(() => wrapper.find('OrganizationsList').setState({ results: [] })); @@ -158,7 +157,7 @@ describe('<_OrganizationsList />', () => { expect(api.destroyOrganization).toHaveBeenCalledTimes(component.state('results').length); }); - test('call fetchOrganizations after org(s) have been deleted', async () => { + test('call fetchOrganizations after org(s) have been deleted', () => { const fetchOrgs = jest.spyOn(_OrganizationsList.prototype, 'fetchOrganizations'); const event = { preventDefault: () => { } }; wrapper = mountWithContexts( @@ -171,11 +170,11 @@ describe('<_OrganizationsList />', () => { selected: mockAPIOrgsList.data.results.slice(0, 1) }); const component = wrapper.find('OrganizationsList'); - await component.instance().handleOrgDelete(event); + component.instance().handleOrgDelete(event); expect(fetchOrgs).toBeCalled(); }); - test('url updates properly', async () => { + test('url updates properly', () => { const history = createMemoryHistory({ initialEntries: ['organizations?order_by=name&page=1&page_size=5'], }); @@ -193,7 +192,7 @@ describe('<_OrganizationsList />', () => { expect(history.location.search).toBe('?order_by=modified&page=1&page_size=5'); }); - test('onSort sends the correct information to fetchOrganizations', async () => { + test('onSort sends the correct information to fetchOrganizations', () => { const history = createMemoryHistory({ initialEntries: ['organizations?order_by=name&page=1&page_size=5'], }); @@ -226,15 +225,16 @@ describe('<_OrganizationsList />', () => { } } ); - await setImmediate(async () => { - wrapper.setState({ - results: mockAPIOrgsList.data.results, - selected: [...mockAPIOrgsList.data.results].push({ id: 'a' }) - }); - wrapper.update(); + await wrapper.setState({ + results: mockAPIOrgsList.data.results, + selected: [...mockAPIOrgsList.data.results].push({ + name: 'Organization 6', + id: 'a', + }) }); + wrapper.update(); const component = wrapper.find('OrganizationsList'); component.instance().handleOrgDelete(); - expect(handleError).toBeCalled(); + expect(handleError).toHaveBeenCalled(); }); }); From f71421f60ab63da3bec31328d13e0d8c398be41a Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Tue, 30 Apr 2019 10:22:25 -0400 Subject: [PATCH 4/4] removed orgsToDelete and fixed other tests --- .../screens/OrganizationsList.test.jsx | 17 +++++++++-------- .../Organizations/screens/OrganizationsList.jsx | 1 - 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/__tests__/pages/Organizations/screens/OrganizationsList.test.jsx b/__tests__/pages/Organizations/screens/OrganizationsList.test.jsx index 410a521aa6..1262c36db8 100644 --- a/__tests__/pages/Organizations/screens/OrganizationsList.test.jsx +++ b/__tests__/pages/Organizations/screens/OrganizationsList.test.jsx @@ -56,7 +56,7 @@ describe('<_OrganizationsList />', () => { beforeEach(() => { api = { - getOrganizations: jest.fn(), + getOrganizations: () => {}, destroyOrganization: jest.fn(), }; }); @@ -76,6 +76,7 @@ describe('<_OrganizationsList />', () => { results: mockAPIOrgsList.data.results }); wrapper.update(); + expect(wrapper.state('selected').length).toBe(0); wrapper.instance().onSelect(mockAPIOrgsList.data.results.slice(0, 1)); expect(wrapper.state('selected').length).toBe(1); }); @@ -87,12 +88,12 @@ describe('<_OrganizationsList />', () => { wrapper.setState( mockAPIOrgsList.data ); - wrapper.find({ type: 'checkbox' }).simulate('click'); + expect(wrapper.state('selected').length).toBe(0); wrapper.instance().onSelectAll(true); expect(wrapper.find('OrganizationsList').state().selected.length).toEqual(wrapper.state().results.length); }); - test('orgsToDelete is 0 when close modal button is clicked.', () => { + test('selected is > 0 when close modal button is clicked.', () => { wrapper = mountWithContexts( ); @@ -108,11 +109,11 @@ describe('<_OrganizationsList />', () => { button.prop('onClose')(); wrapper.update(); expect(component.state('isModalOpen')).toBe(false); - expect(component.state('selected').length).toBe(0); + expect(component.state('selected').length).toBeGreaterThan(0); wrapper.unmount(); }); - test('orgsToDelete is 0 when cancel modal button is clicked.', () => { + test('selected is > 0 when cancel modal button is clicked.', () => { wrapper = mountWithContexts( ); @@ -128,11 +129,11 @@ describe('<_OrganizationsList />', () => { button.prop('onClick')(); wrapper.update(); expect(component.state('isModalOpen')).toBe(false); - expect(component.state('selected').length).toBe(0); + expect(component.state('selected').length).toBeGreaterThan(0); wrapper.unmount(); }); - test('api is called to delete Orgs for each org in orgsToDelete.', () => { + test('api is called to delete Orgs for each org in selected.', () => { const fetchOrganizations = jest.fn(() => wrapper.find('OrganizationsList').setState({ results: [] })); @@ -154,7 +155,7 @@ describe('<_OrganizationsList />', () => { const button = wrapper.find('ModalBoxFooter').find('button').at(0); button.simulate('click'); wrapper.update(); - expect(api.destroyOrganization).toHaveBeenCalledTimes(component.state('results').length); + expect(api.destroyOrganization).toHaveBeenCalledTimes(component.state('selected').length); }); test('call fetchOrganizations after org(s) have been deleted', () => { diff --git a/src/pages/Organizations/screens/OrganizationsList.jsx b/src/pages/Organizations/screens/OrganizationsList.jsx index fba7a5b790..721b545e95 100644 --- a/src/pages/Organizations/screens/OrganizationsList.jsx +++ b/src/pages/Organizations/screens/OrganizationsList.jsx @@ -146,7 +146,6 @@ class OrganizationsList extends Component { handleClearOrgDeleteModal () { this.setState({ isModalOpen: false, - selected: [] }); }