From 0f5624e59711578884518b4438c9c7e5c47ade68 Mon Sep 17 00:00:00 2001 From: Pascal Mugnier Date: Mon, 9 Jul 2018 10:43:05 +0200 Subject: [PATCH] SONAR-10964 Display of group members is incorrect after search --- .../js/apps/groups/components/EditMembers.tsx | 104 +--- .../groups/components/EditMembersModal.tsx | 157 +++++ .../components/__tests__/EditMembers-test.tsx | 10 +- .../__tests__/EditMembersModal-test.tsx | 52 ++ .../__snapshots__/EditMembers-test.tsx.snap | 563 ++++++++++++++++-- .../EditMembersModal-test.tsx.snap | 91 +++ 6 files changed, 838 insertions(+), 139 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/groups/components/EditMembersModal.tsx create mode 100644 server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembersModal-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembersModal-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx b/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx index ca563669161..b149628988d 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx @@ -18,19 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { find, without } from 'lodash'; +import EditMembersModal from './EditMembersModal'; import { Group } from '../../../app/types'; -import Modal from '../../../components/controls/Modal'; import BulletListIcon from '../../../components/icons-components/BulletListIcon'; -import SelectList, { Filter } from '../../../components/SelectList/SelectList'; -import { ButtonIcon, ResetButtonLink } from '../../../components/ui/buttons'; -import { translate } from '../../../helpers/l10n'; -import { - getUsersInGroup, - addUserToGroup, - removeUserFromGroup, - GroupUser -} from '../../../api/user_groups'; +import { ButtonIcon } from '../../../components/ui/buttons'; interface Props { group: Group; @@ -40,17 +31,14 @@ interface Props { interface State { modal: boolean; - users: GroupUser[]; - selectedUsers: string[]; } export default class EditMembers extends React.PureComponent { container?: HTMLElement | null; mounted = false; - state: State = { modal: false, users: [], selectedUsers: [] }; + state: State = { modal: false }; componentDidMount() { - this.handleSearch('', Filter.Selected); this.mounted = true; } @@ -58,48 +46,6 @@ export default class EditMembers extends React.PureComponent { this.mounted = false; } - handleSearch = (query: string, selected: Filter) => { - return getUsersInGroup({ - name: this.props.group.name, - organization: this.props.organization, - ps: 100, - q: query !== '' ? query : undefined, - selected - }).then( - data => { - this.setState({ - users: data.users, - selectedUsers: data.users.filter(user => user.selected).map(user => user.login) - }); - }, - () => {} - ); - }; - - handleSelect = (login: string) => { - return addUserToGroup({ - name: this.props.group.name, - login, - organization: this.props.organization - }).then(() => { - this.setState((state: State) => ({ - selectedUsers: [...state.selectedUsers, login] - })); - }); - }; - - handleUnselect = (login: string) => { - return removeUserFromGroup({ - name: this.props.group.name, - login, - organization: this.props.organization - }).then(() => { - this.setState((state: State) => ({ - selectedUsers: without(state.selectedUsers, login) - })); - }); - }; - handleMembersClick = () => { this.setState({ modal: true }); }; @@ -111,52 +57,18 @@ export default class EditMembers extends React.PureComponent { } }; - renderElement = (login: string): React.ReactNode => { - const user = find(this.state.users, { login }); - return ( -
- {user === undefined ? ( - login - ) : ( - <> - {user.name} -
- {user.login} - - )} -
- ); - }; - render() { - const modalHeader = translate('users.update'); - return ( <> {this.state.modal && ( - -
-

{modalHeader}

-
- -
- user.login)} - onSearch={this.handleSearch} - onSelect={this.handleSelect} - onUnselect={this.handleUnselect} - renderElement={this.renderElement} - selectedElements={this.state.selectedUsers} - /> -
- -
- {translate('Done')} -
-
+ )} ); diff --git a/server/sonar-web/src/main/js/apps/groups/components/EditMembersModal.tsx b/server/sonar-web/src/main/js/apps/groups/components/EditMembersModal.tsx new file mode 100644 index 00000000000..097ff47354b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/EditMembersModal.tsx @@ -0,0 +1,157 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { find, without } from 'lodash'; +import Modal from '../../../components/controls/Modal'; +import SelectList, { Filter } from '../../../components/SelectList/SelectList'; +import { ResetButtonLink } from '../../../components/ui/buttons'; +import { translate } from '../../../helpers/l10n'; +import { + GroupUser, + removeUserFromGroup, + addUserToGroup, + getUsersInGroup +} from '../../../api/user_groups'; +import { Group } from '../../../app/types'; +import DeferredSpinner from '../../../components/common/DeferredSpinner'; + +interface Props { + group: Group; + onClose: () => void; + organization: string | undefined; +} + +interface State { + loading: boolean; + users: GroupUser[]; + selectedUsers: string[]; +} + +export default class EditMembers extends React.PureComponent { + mounted = false; + state: State = { loading: true, users: [], selectedUsers: [] }; + + componentDidMount() { + this.mounted = true; + this.handleSearch('', Filter.Selected); + } + + componentWillUnmount() { + this.mounted = false; + } + + handleSearch = (query: string, selected: Filter) => { + return getUsersInGroup({ + name: this.props.group.name, + organization: this.props.organization, + ps: 100, + q: query !== '' ? query : undefined, + selected + }).then( + data => { + if (this.mounted) { + this.setState({ + loading: false, + users: data.users, + selectedUsers: data.users.filter(user => user.selected).map(user => user.login) + }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + }; + + handleSelect = (login: string) => { + return addUserToGroup({ + name: this.props.group.name, + login, + organization: this.props.organization + }).then(() => { + if (this.mounted) { + this.setState((state: State) => ({ + selectedUsers: [...state.selectedUsers, login] + })); + } + }); + }; + + handleUnselect = (login: string) => { + return removeUserFromGroup({ + name: this.props.group.name, + login, + organization: this.props.organization + }).then(() => { + if (this.mounted) { + this.setState((state: State) => ({ + selectedUsers: without(state.selectedUsers, login) + })); + } + }); + }; + + renderElement = (login: string): React.ReactNode => { + const user = find(this.state.users, { login }); + return ( +
+ {user === undefined ? ( + login + ) : ( + <> + {user.name} +
+ {user.login} + + )} +
+ ); + }; + + render() { + const modalHeader = translate('users.update'); + return ( + +
+

{modalHeader}

+
+ +
+ + user.login)} + onSearch={this.handleSearch} + onSelect={this.handleSelect} + onUnselect={this.handleUnselect} + renderElement={this.renderElement} + selectedElements={this.state.selectedUsers} + /> + +
+ +
+ {translate('Done')} +
+
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx index a480e466c8a..44311262af0 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx @@ -18,20 +18,22 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import EditMembers from '../EditMembers'; -import { click } from '../../../../helpers/testUtils'; +import { click, waitAndUpdate } from '../../../../helpers/testUtils'; -it('should edit members', () => { +it('should edit members', async () => { const group = { id: 3, name: 'Foo', membersCount: 5 }; const onEdit = jest.fn(); - const wrapper = shallow(); + const wrapper = mount(); expect(wrapper).toMatchSnapshot(); click(wrapper.find('ButtonIcon')); + await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); + await waitAndUpdate(wrapper); click(wrapper.find('ResetButtonLink')); expect(onEdit).toBeCalled(); expect(wrapper).toMatchSnapshot(); diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembersModal-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembersModal-test.tsx new file mode 100644 index 00000000000..3f5b4a03cd8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembersModal-test.tsx @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/* eslint-disable import/first, import/order */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import EditMembersModal from '../EditMembersModal'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; + +jest.mock('../../../../api/user_groups', () => ({ + getUsersInGroup: jest.fn().mockResolvedValue({ + paging: { pageIndex: 0, pageSize: 10, total: 0 }, + users: [ + { + login: 'foo', + name: 'bar', + selected: true + } + ] + }) +})); + +const getUsersInGroup = require('../../../../api/user_groups').getUsersInGroup as jest.Mock; + +const group = { id: 1, name: 'foo', membersCount: 1 }; + +it('should render modal', async () => { + getUsersInGroup.mockClear(); + + const wrapper = shallow( {}} organization="bar" />); + expect(wrapper).toMatchSnapshot(); + + await waitAndUpdate(wrapper); + expect(getUsersInGroup).toHaveBeenCalledTimes(1); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap index a0b1f679d14..4af0e007372 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap @@ -1,67 +1,552 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should edit members 1`] = ` - + - + + - + `; exports[`should edit members 2`] = ` - + - + + - -
-

- users.update -

-
-
- -
-
- - Done - -
-
-
+ +
+
+
+

+ users.update +

+
+
+ + +
+
+ +
    +
  • + + + + +
  • +
  • + + + + +
  • +
  • + + + + +
  • +
+
+ +
+ + + + + + + + + + +
+
+
+ +
+
    +
+
+
+
+
+
+
+ + + + +
+
+
+
+ + +
+ `; exports[`should edit members 3`] = ` - + - + + - + `; diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembersModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembersModal-test.tsx.snap new file mode 100644 index 00000000000..b9156bda237 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembersModal-test.tsx.snap @@ -0,0 +1,91 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render modal 1`] = ` + +
+

+ users.update +

+
+
+ + + +
+
+ + Done + +
+
+`; + +exports[`should render modal 2`] = ` + +
+

+ users.update +

+
+
+ + + +
+ +
+`; -- 2.39.5