diff options
Diffstat (limited to 'server/sonar-web/src')
29 files changed, 577 insertions, 281 deletions
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.js new file mode 100644 index 00000000000..a609ffce38e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersListHeader.js @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +//@flow +import React from 'react'; +import UsersSearch from '../../users/components/UsersSearch'; +import { formatMeasure } from '../../../helpers/measures'; +import { translate } from '../../../helpers/l10n'; + +type Props = { + handleSearch: (query?: string) => void, + total?: number +}; + +export default class MembersListHeader extends React.PureComponent { + props: Props; + + render() { + const { total } = this.props; + return ( + <div className="panel panel-vertical bordered-bottom spacer-bottom"> + <UsersSearch onSearch={this.props.handleSearch} className="display-inline-block" /> + {total != null && + <span className="pull-right little-spacer-top"> + <strong>{formatMeasure(total, 'INT')}</strong> + {' '} + {translate('organization.members.members')} + </span>} + </div> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js index 3c2f7c8815b..2324b9d4c7f 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js @@ -20,7 +20,7 @@ //@flow import React from 'react'; import Avatar from '../../../components/ui/Avatar'; -import { translate } from '../../../helpers/l10n'; +import { translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; import RemoveMemberForm from './forms/RemoveMemberForm'; import ManageMemberGroupsForm from './forms/ManageMemberGroupsForm'; @@ -47,10 +47,13 @@ export default class MembersListItem extends React.PureComponent { <td className="thin nowrap"> <Avatar hash={member.avatar} email={member.email} size={AVATAR_SIZE} /> </td> - <td className="nowrap text-middle"><strong>{member.name}</strong></td> + <td className="nowrap text-middle"> + <strong>{member.login}</strong> + <span className="note little-spacer-left">{member.name}</span> + </td> {organization.canAdmin && <td className="text-right text-middle"> - {translate('organization.members.x_group(s)', formatMeasure(member.groupCount, 'INT'))} + {translateWithParameters('organization.members.x_groups', formatMeasure(member.groupCount || 0, 'INT'))} </td>} {organization.canAdmin && <td className="nowrap text-middle text-right"> @@ -62,18 +65,18 @@ export default class MembersListItem extends React.PureComponent { </button> <ul className="dropdown-menu dropdown-menu-right"> <li> - <RemoveMemberForm + <ManageMemberGroupsForm + organizationGroups={this.props.organizationGroups} organization={this.props.organization} - removeMember={this.props.removeMember} + updateMemberGroups={this.props.updateMemberGroups} member={this.props.member} /> </li> <li role="separator" className="divider" /> <li> - <ManageMemberGroupsForm - organizationGroups={this.props.organizationGroups} + <RemoveMemberForm organization={this.props.organization} - updateMemberGroups={this.props.updateMemberGroups} + removeMember={this.props.removeMember} member={this.props.member} /> </li> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/PageHeader.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersPageHeader.js index 7ea0ae70c4d..1a8f6999f77 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/PageHeader.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersPageHeader.js @@ -19,8 +19,6 @@ */ //@flow import React from 'react'; -import { formatMeasure } from '../../../helpers/measures'; -import { translate } from '../../../helpers/l10n'; type Props = { loading: boolean, @@ -28,7 +26,7 @@ type Props = { children?: {} }; -export default class PageHeader extends React.PureComponent { +export default class MembersPageHeader extends React.PureComponent { props: Props; render() { @@ -36,12 +34,6 @@ export default class PageHeader extends React.PureComponent { <header className="page-header"> {this.props.loading && <i className="spinner" />} {this.props.children} - {this.props.total != null && - <span className="page-totalcount"> - <strong>{formatMeasure(this.props.total, 'INT')}</strong> - {' '} - {translate('organization.members.member(s)')} - </span>} </header> ); } diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.js new file mode 100644 index 00000000000..f21f52b0094 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.js @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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. + */ +//@flow +import React from 'react'; +import Checkbox from '../../../components/controls/Checkbox'; +import type { OrgGroup } from '../../../store/organizations/duck'; + +type Props = { + group: OrgGroup, + checked: boolean, + onCheck: (string, boolean) => void +}; + +export default class OrganizationGroupCheckbox extends React.PureComponent { + props: Props; + + onCheck = (checked: boolean) => { + this.props.onCheck(this.props.group.id, checked); + }; + + toggleCheck = () => { + this.props.onCheck(this.props.group.id, !this.props.checked); + }; + + render() { + return ( + <li + className="capitalize list-item-checkable-link" + onClick={this.toggleCheck} + tabIndex={0} + role="listitem" + > + <Checkbox checked={this.props.checked} onCheck={this.onCheck} /> + {' '}{this.props.group.name} + </li> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js index 6caa05de692..b4448548149 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js @@ -19,10 +19,10 @@ */ // @flow import React from 'react'; -import PageHeader from './PageHeader'; +import MembersPageHeader from './MembersPageHeader'; +import MembersListHeader from './MembersListHeader'; import MembersList from './MembersList'; import AddMemberForm from './forms/AddMemberForm'; -import UsersSearch from '../../users/components/UsersSearch'; import ListFooter from '../../../components/controls/ListFooter'; import type { Organization, OrgGroup } from '../../../store/organizations/duck'; import type { Member } from '../../../store/organizationsMembers/actions'; @@ -38,18 +38,19 @@ type Props = { fetchOrganizationGroups: (organizationKey: string) => void, addOrganizationMember: (organizationKey: string, member: Member) => void, removeOrganizationMember: (organizationKey: string, member: Member) => void, - updateOrganizationMemberGroups: (member: Member, add: Array<string>, remove: Array<string>) => void, + updateOrganizationMemberGroups: ( + member: Member, + add: Array<string>, + remove: Array<string> + ) => void }; export default class OrganizationMembers extends React.PureComponent { props: Props; componentDidMount() { - const notLoadedYet = this.props.members.length < 1 || this.props.status.query != null; - if (!this.props.loading && notLoadedYet) { - this.handleSearchMembers(); - } - if (this.props.organizationGroups.length <= 0) { + this.handleSearchMembers(); + if (this.props.organization.canAdmin) { this.props.fetchOrganizationGroups(this.props.organization.key); } } @@ -74,15 +75,15 @@ export default class OrganizationMembers extends React.PureComponent { const { organization, status, members } = this.props; return ( <div className="page page-limited"> - <PageHeader loading={status.loading} total={status.total}> + <MembersPageHeader loading={status.loading} total={status.total}> {organization.canAdmin && <div className="page-actions"> <div className="button-group"> <AddMemberForm memberLogins={this.props.memberLogins} addMember={this.addMember} /> </div> </div>} - </PageHeader> - <UsersSearch onSearch={this.handleSearchMembers} /> + </MembersPageHeader> + <MembersListHeader total={status.total} handleSearch={this.handleSearchMembers} /> <MembersList members={members} organizationGroups={this.props.organizationGroups} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.js new file mode 100644 index 00000000000..439f998b4b7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListHeader-test.js @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import { shallow } from 'enzyme'; +import MembersListHeader from '../MembersListHeader'; + +it('should render without the total', () => { + const wrapper = shallow( + <MembersListHeader handleSearch={jest.fn()} /> + ); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render with the total', () => { + const wrapper = shallow( + <MembersListHeader handleSearch={jest.fn()} total={8} /> + ); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js index d0cb52edbca..dd97722ea42 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersListItem-test.js @@ -23,13 +23,13 @@ import MembersListItem from '../MembersListItem'; const organization = { key: 'foo', name: 'Foo' }; const admin = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; -const john = { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af', groupCount: 1 }; +const john = { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af' }; it('should not render actions and groups for non admin', () => { const wrapper = shallow( <MembersListItem organization={organization} - member={john} + member={admin} /> ); expect(wrapper).toMatchSnapshot(); @@ -44,3 +44,13 @@ it('should render actions and groups for admin', () => { ); expect(wrapper).toMatchSnapshot(); }); + +it('should groups at 0 if the groupCount field is not defined (just added user)', () => { + const wrapper = shallow( + <MembersListItem + organization={{ ...organization, canAdmin: true }} + member={john} + /> + ); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/PageHeader-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersPageHeader-test.js index 06f9ef06e90..4e8fd9a2760 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/PageHeader-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/MembersPageHeader-test.js @@ -19,11 +19,11 @@ */ import React from 'react'; import { shallow } from 'enzyme'; -import PageHeader from '../PageHeader'; +import MembersPageHeader from '../MembersPageHeader'; it('should render the members page header', () => { const wrapper = shallow( - <PageHeader /> + <MembersPageHeader /> ); expect(wrapper).toMatchSnapshot(); wrapper.setProps({ loading: true }); @@ -31,15 +31,15 @@ it('should render the members page header', () => { }); it('should render the members page header with the total', () => { - const wrapper = shallow(<PageHeader total="5" />); + const wrapper = shallow(<MembersPageHeader total="5" />); expect(wrapper).toMatchSnapshot(); }); it('should render its children', () => { const wrapper = shallow( - <PageHeader loading={true} total="5"> + <MembersPageHeader loading={true} total="5"> <span>children test</span> - </PageHeader> + </MembersPageHeader> ); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.js new file mode 100644 index 00000000000..146e8aacba1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.js @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import { shallow } from 'enzyme'; +import OrganizationGroupCheckbox from '../OrganizationGroupCheckbox'; + +const group = { + id: '7', + name: 'professionals', + description: '', + membersCount: 12 +}; + +it('should render unchecked', () => { + const wrapper = shallow( + <OrganizationGroupCheckbox group={group} checked={false} onCheck={jest.fn()} /> + ); + expect(wrapper).toMatchSnapshot(); +}); + +it('should be able to toggle check', () => { + const onCheck = jest.fn((group, checked) => wrapper.setProps({ checked })); + const wrapper = shallow( + <OrganizationGroupCheckbox group={group} checked={true} onCheck={onCheck} /> + ); + expect(wrapper).toMatchSnapshot(); + wrapper.instance().toggleCheck(); + expect(onCheck.mock.calls).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js index 461722d4b3e..9be07a22d0b 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationMembers-test.js @@ -27,8 +27,6 @@ const members = [ { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af', groupCount: 1 } ]; const status = { total: members.length }; -const fetchOrganizationMembers = jest.fn(); -const fetchMoreOrganizationMembers = jest.fn(); it('should not render actions for non admin', () => { const wrapper = shallow( @@ -36,8 +34,8 @@ it('should not render actions for non admin', () => { organization={organization} members={members} status={status} - fetchOrganizationMembers={fetchOrganizationMembers} - fetchMoreOrganizationMembers={fetchMoreOrganizationMembers} + fetchOrganizationMembers={jest.fn()} + fetchMoreOrganizationMembers={jest.fn()} /> ); expect(wrapper).toMatchSnapshot(); @@ -49,8 +47,8 @@ it('should render actions for admin', () => { organization={{ ...organization, canAdmin: true }} members={members} status={{ ...status, loading: true }} - fetchOrganizationMembers={fetchOrganizationMembers} - fetchMoreOrganizationMembers={fetchMoreOrganizationMembers} + fetchOrganizationMembers={jest.fn()} + fetchMoreOrganizationMembers={jest.fn()} /> ); expect(wrapper).toMatchSnapshot(); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.js.snap new file mode 100644 index 00000000000..1d320f196fc --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListHeader-test.js.snap @@ -0,0 +1,25 @@ +exports[`test should render with the total 1`] = ` +<div + className="panel panel-vertical bordered-bottom spacer-bottom"> + <UsersSearch + className="display-inline-block" + onSearch={[Function]} /> + <span + className="pull-right little-spacer-top"> + <strong> + 8 + </strong> + + organization.members.members + </span> +</div> +`; + +exports[`test should render without the total 1`] = ` +<div + className="panel panel-vertical bordered-bottom spacer-bottom"> + <UsersSearch + className="display-inline-block" + onSearch={[Function]} /> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap index 02e1106b6b9..39076a98a53 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersListItem-test.js.snap @@ -1,4 +1,4 @@ -exports[`test should not render actions and groups for non admin 1`] = ` +exports[`test should groups at 0 if the groupCount field is not defined (just added user) 1`] = ` <tr> <td className="thin nowrap"> @@ -9,8 +9,92 @@ exports[`test should not render actions and groups for non admin 1`] = ` <td className="nowrap text-middle"> <strong> + john + </strong> + <span + className="note little-spacer-left"> John Doe + </span> + </td> + <td + className="text-right text-middle"> + organization.members.x_groups.0 + </td> + <td + className="nowrap text-middle text-right"> + <div + className="dropdown"> + <button + className="dropdown-toggle little-spacer-right" + data-toggle="dropdown"> + <i + className="icon-settings" /> + + <i + className="icon-dropdown" /> + </button> + <ul + className="dropdown-menu dropdown-menu-right"> + <li> + <ManageMemberGroupsForm + member={ + Object { + "avatar": "7daf6c79d4802916d83f6266e24850af", + "login": "john", + "name": "John Doe", + } + } + organization={ + Object { + "canAdmin": true, + "key": "foo", + "name": "Foo", + } + } /> + </li> + <li + className="divider" + role="separator" /> + <li> + <RemoveMemberForm + member={ + Object { + "avatar": "7daf6c79d4802916d83f6266e24850af", + "login": "john", + "name": "John Doe", + } + } + organization={ + Object { + "canAdmin": true, + "key": "foo", + "name": "Foo", + } + } /> + </li> + </ul> + </div> + </td> +</tr> +`; + +exports[`test should not render actions and groups for non admin 1`] = ` +<tr> + <td + className="thin nowrap"> + <Connect(Avatar) + hash="" + size={36} /> + </td> + <td + className="nowrap text-middle"> + <strong> + admin </strong> + <span + className="note little-spacer-left"> + Admin Istrator + </span> </td> </tr> `; @@ -26,12 +110,16 @@ exports[`test should render actions and groups for admin 1`] = ` <td className="nowrap text-middle"> <strong> - Admin Istrator + admin </strong> + <span + className="note little-spacer-left"> + Admin Istrator + </span> </td> <td className="text-right text-middle"> - organization.members.x_group(s).3 + organization.members.x_groups.3 </td> <td className="nowrap text-middle text-right"> @@ -49,7 +137,7 @@ exports[`test should render actions and groups for admin 1`] = ` <ul className="dropdown-menu dropdown-menu-right"> <li> - <RemoveMemberForm + <ManageMemberGroupsForm member={ Object { "avatar": "", @@ -70,7 +158,7 @@ exports[`test should render actions and groups for admin 1`] = ` className="divider" role="separator" /> <li> - <ManageMemberGroupsForm + <RemoveMemberForm member={ Object { "avatar": "", diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/PageHeader-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersPageHeader-test.js.snap index 9e31b2215cd..dd2b2349739 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/PageHeader-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersPageHeader-test.js.snap @@ -6,14 +6,6 @@ exports[`test should render its children 1`] = ` <span> children test </span> - <span - className="page-totalcount"> - <strong> - 5 - </strong> - - organization.members.member(s) - </span> </header> `; @@ -29,14 +21,5 @@ exports[`test should render the members page header 2`] = ` exports[`test should render the members page header with the total 1`] = ` <header - className="page-header"> - <span - className="page-totalcount"> - <strong> - 5 - </strong> - - organization.members.member(s) - </span> -</header> + className="page-header" /> `; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.js.snap new file mode 100644 index 00000000000..2ecdb53cf0d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.js.snap @@ -0,0 +1,53 @@ +exports[`test should be able to toggle check 1`] = ` +<li + className="capitalize list-item-checkable-link" + onClick={[Function]} + role="listitem" + tabIndex={0}> + <Checkbox + checked={true} + onCheck={[Function]} + thirdState={false} /> + + professionals +</li> +`; + +exports[`test should be able to toggle check 2`] = ` +Array [ + Array [ + "7", + false, + ], +] +`; + +exports[`test should be able to toggle check 3`] = ` +<li + className="capitalize list-item-checkable-link" + onClick={[Function]} + role="listitem" + tabIndex={0}> + <Checkbox + checked={false} + onCheck={[Function]} + thirdState={false} /> + + professionals +</li> +`; + +exports[`test should render unchecked 1`] = ` +<li + className="capitalize list-item-checkable-link" + onClick={[Function]} + role="listitem" + tabIndex={0}> + <Checkbox + checked={false} + onCheck={[Function]} + thirdState={false} /> + + professionals +</li> +`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap index e5fd4a477e0..bcd0da07201 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap @@ -1,10 +1,11 @@ exports[`test should not render actions for non admin 1`] = ` <div className="page page-limited"> - <PageHeader + <MembersPageHeader + total={2} /> + <MembersListHeader + handleSearch={[Function]} total={2} /> - <UsersSearch - onSearch={[Function]} /> <MembersList members={ Array [ @@ -40,7 +41,7 @@ exports[`test should not render actions for non admin 1`] = ` exports[`test should render actions for admin 1`] = ` <div className="page page-limited"> - <PageHeader + <MembersPageHeader loading={true} total={2}> <div @@ -51,9 +52,10 @@ exports[`test should render actions for admin 1`] = ` addMember={[Function]} /> </div> </div> - </PageHeader> - <UsersSearch - onSearch={[Function]} /> + </MembersPageHeader> + <MembersListHeader + handleSearch={[Function]} + total={2} /> <MembersList members={ Array [ diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js index 554b363dd35..ac19d3e725f 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/ManageMemberGroupsForm.js @@ -21,9 +21,9 @@ import React from 'react'; import Modal from 'react-modal'; import { keyBy, pickBy } from 'lodash'; -import Checkbox from '../../../../components/controls/Checkbox'; import { getUserGroups } from '../../../../api/users'; import { translate, translateWithParameters } from '../../../../helpers/l10n'; +import OrganizationGroupCheckbox from '../OrganizationGroupCheckbox'; import type { Member } from '../../../../store/organizationsMembers/actions'; import type { Organization, OrgGroup } from '../../../../store/organizations/duck'; @@ -47,15 +47,14 @@ export default class ManageMemberGroupsForm extends React.PureComponent { open: false }; - openForm = () => { - if (!this.state.userGroups) { - this.loadUserGroups(); - } + openForm = (evt: MouseEvent) => { + evt.preventDefault(); + this.loadUserGroups(); this.setState({ open: true }); }; closeForm = () => { - this.setState({ userGroups: undefined, open: false }); + this.setState({ open: false }); }; loadUserGroups = () => { @@ -77,7 +76,7 @@ export default class ManageMemberGroupsForm extends React.PureComponent { return false; }; - onCheck = (checked: boolean, groupId: string) => { + onCheck = (groupId: string, checked: boolean) => { this.setState((prevState: State) => { const userGroups = prevState.userGroups || {}; const group = userGroups[groupId] || {}; @@ -93,11 +92,12 @@ export default class ManageMemberGroupsForm extends React.PureComponent { handleSubmit = (e: Object) => { e.preventDefault(); - this.props.updateMemberGroups(this.props.member, + this.props.updateMemberGroups( + this.props.member, Object.keys(pickBy(this.state.userGroups, group => group.status === 'add')), Object.keys(pickBy(this.state.userGroups, group => group.status === 'remove')) ); - this.setState({ open: false }); + this.closeForm(); }; renderModal() { @@ -115,19 +115,17 @@ export default class ManageMemberGroupsForm extends React.PureComponent { <form onSubmit={this.handleSubmit}> <div className="modal-body"> <strong> - {translateWithParameters('organization.members.x_groups', this.props.member.name)} + {translateWithParameters('organization.members.members_groups', this.props.member.name)} </strong>{' '}{this.state.loading && <i className="spinner" />} {!this.state.loading && <ul className="list-spaced"> {this.props.organizationGroups.map(group => ( - <li className="capitalize" key={group.id}> - <Checkbox - id={group.id} - checked={this.isGroupSelected(group.id)} - onCheck={this.onCheck} - /> - {' '}{group.name} - </li> + <OrganizationGroupCheckbox + key={group.id} + group={group} + checked={this.isGroupSelected(group.id)} + onCheck={this.onCheck} + /> ))} </ul>} </div> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js index 6d4c9e59adf..29bccf54aca 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/RemoveMemberForm.js @@ -41,7 +41,8 @@ export default class RemoveMemberForm extends React.PureComponent { open: false }; - openForm = () => { + openForm = (evt: MouseEvent) => { + evt.preventDefault(); this.setState({ open: true }); }; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js index 403975ed4a1..1874608a815 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/AddMemberForm-test.js @@ -23,17 +23,16 @@ import { click } from '../../../../../helpers/testUtils'; import AddMemberForm from '../AddMemberForm'; const memberLogins = ['admin']; -const addMember = jest.fn(); it('should render and open the modal', () => { - const wrapper = shallow(<AddMemberForm memberLogins={memberLogins} addMember={addMember} />); + const wrapper = shallow(<AddMemberForm memberLogins={memberLogins} addMember={jest.fn()} />); expect(wrapper).toMatchSnapshot(); wrapper.setState({ open: true }); expect(wrapper).toMatchSnapshot(); }); it('should correctly handle user interactions', () => { - const wrapper = mount(<AddMemberForm memberLogins={memberLogins} addMember={addMember} />); + const wrapper = mount(<AddMemberForm memberLogins={memberLogins} addMember={jest.fn()} />); click(wrapper.find('button')); expect(wrapper.state('open')).toBeTruthy(); wrapper.instance().closeForm(); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js index 4d7eb28c9bb..224a1222658 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/ManageMemberGroupsForm-test.js @@ -47,15 +47,14 @@ const organizationGroups = [ const userGroups = { 11: { id: 11, name: 'pull-request-analysers', description: 'Technical accounts', selected: true } }; -const updateMemberGroups = jest.fn(); -const getMountedForm = function() { +const getMountedForm = function(updateFunc = jest.fn()) { const wrapper = mount( <ManageMemberGroupsForm member={member} organization={organization} organizationGroups={organizationGroups} - updateMemberGroups={updateMemberGroups} + updateMemberGroups={updateFunc} /> ); const instance = wrapper.instance(); @@ -71,7 +70,7 @@ it('should render and open the modal', () => { member={member} organization={organization} organizationGroups={organizationGroups} - updateMemberGroups={updateMemberGroups} + updateMemberGroups={jest.fn()} /> ); expect(wrapper).toMatchSnapshot(); @@ -89,34 +88,34 @@ it('should correctly handle user interactions', () => { it('should correctly select the groups', () => { const form = getMountedForm(); - form.instance.openForm(); + form.instance.openForm(mockEvent); expect(form.instance.isGroupSelected(11)).toBeTruthy(); expect(form.instance.isGroupSelected(7)).toBeFalsy(); - form.instance.onCheck(false, 11); - form.instance.onCheck(true, 7); + form.instance.onCheck(11, false); + form.instance.onCheck(7, true); expect(form.wrapper.state('userGroups')).toMatchSnapshot(); expect(form.instance.isGroupSelected(11)).toBeFalsy(); expect(form.instance.isGroupSelected(7)).toBeTruthy(); }); it('should correctly handle the submit event and close the modal', () => { - const form = getMountedForm(); - form.instance.openForm(); - form.instance.onCheck(false, 11); - form.instance.onCheck(true, 7); + const updateMemberGroups = jest.fn(); + const form = getMountedForm(updateMemberGroups); + form.instance.openForm(mockEvent); + form.instance.onCheck(11, false); + form.instance.onCheck(7, true); form.instance.handleSubmit(mockEvent); expect(updateMemberGroups.mock.calls).toMatchSnapshot(); expect(form.wrapper.state()).toMatchSnapshot(); - form.instance.openForm(); - expect(form.wrapper.state()).toMatchSnapshot(); }); -it('should reset the selected groups when the modal is closed', () => { +it('should reset the selected groups when the modal is opened', () => { const form = getMountedForm(); - form.instance.openForm(); - form.instance.onCheck(false, 11); - form.instance.onCheck(true, 7); + form.instance.openForm(mockEvent); + form.instance.onCheck(11, false); + form.instance.onCheck(7, true); expect(form.wrapper.state()).toMatchSnapshot(); form.instance.closeForm(); + form.instance.openForm(mockEvent); expect(form.wrapper.state()).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js index e8f813e2a0d..427559eb011 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/RemoveMemberForm-test.js @@ -23,12 +23,11 @@ import { click, mockEvent } from '../../../../../helpers/testUtils'; import RemoveMemberForm from '../RemoveMemberForm'; const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; -const removeMember = jest.fn(); const organization = { name: 'MyOrg' }; it('should render and open the modal', () => { const wrapper = shallow( - <RemoveMemberForm member={member} removeMember={removeMember} organization={organization} /> + <RemoveMemberForm member={member} removeMember={jest.fn()} organization={organization} /> ); expect(wrapper).toMatchSnapshot(); wrapper.setState({ open: true }); @@ -36,6 +35,7 @@ it('should render and open the modal', () => { }); it('should correctly handle user interactions', () => { + const removeMember = jest.fn(); const wrapper = mount( <RemoveMemberForm member={member} removeMember={removeMember} organization={organization} /> ); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap index 2e069febb73..a15557608a4 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/ManageMemberGroupsForm-test.js.snap @@ -36,25 +36,6 @@ Object { } `; -exports[`test should correctly handle the submit event and close the modal 3`] = ` -Object { - "loading": false, - "open": true, - "userGroups": Object { - "11": Object { - "description": "Technical accounts", - "id": 11, - "name": "pull-request-analysers", - "selected": true, - "status": "remove", - }, - "7": Object { - "status": "add", - }, - }, -} -`; - exports[`test should correctly handle user interactions 1`] = ` Object { "loading": false, @@ -120,41 +101,44 @@ exports[`test should render and open the modal 2`] = ` <div className="modal-body"> <strong> - organization.members.x_groups.Admin Istrator + organization.members.members_groups.Admin Istrator </strong> <ul className="list-spaced"> - <li - className="capitalize"> - <Checkbox - checked={false} - id="7" - onCheck={[Function]} - thirdState={false} /> - - professionals - </li> - <li - className="capitalize"> - <Checkbox - checked={false} - id="11" - onCheck={[Function]} - thirdState={false} /> - - pull-request-analysers - </li> - <li - className="capitalize"> - <Checkbox - checked={false} - id="1" - onCheck={[Function]} - thirdState={false} /> - - sonar-administrators - </li> + <OrganizationGroupCheckbox + checked={false} + group={ + Object { + "description": "", + "id": "7", + "membersCount": 12, + "name": "professionals", + } + } + onCheck={[Function]} /> + <OrganizationGroupCheckbox + checked={false} + group={ + Object { + "description": "Technical accounts", + "id": "11", + "membersCount": 3, + "name": "pull-request-analysers", + } + } + onCheck={[Function]} /> + <OrganizationGroupCheckbox + checked={false} + group={ + Object { + "description": "System administrators", + "id": "1", + "membersCount": 17, + "name": "sonar-administrators", + } + } + onCheck={[Function]} /> </ul> </div> <footer @@ -177,7 +161,7 @@ exports[`test should render and open the modal 2`] = ` </a> `; -exports[`test should reset the selected groups when the modal is closed 1`] = ` +exports[`test should reset the selected groups when the modal is opened 1`] = ` Object { "loading": false, "open": true, @@ -196,10 +180,17 @@ Object { } `; -exports[`test should reset the selected groups when the modal is closed 2`] = ` +exports[`test should reset the selected groups when the modal is opened 2`] = ` Object { "loading": false, - "open": false, - "userGroups": undefined, + "open": true, + "userGroups": Object { + "11": Object { + "description": "Technical accounts", + "id": 11, + "name": "pull-request-analysers", + "selected": true, + }, + }, } `; diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSearch.js b/server/sonar-web/src/main/js/apps/users/components/UsersSearch.js index 1b8290cd35d..d09b9cbb93c 100644 --- a/server/sonar-web/src/main/js/apps/users/components/UsersSearch.js +++ b/server/sonar-web/src/main/js/apps/users/components/UsersSearch.js @@ -24,7 +24,8 @@ import classNames from 'classnames'; import { translate, translateWithParameters } from '../../../helpers/l10n'; type Props = { - onSearch: (query?: string) => void + onSearch: (query?: string) => void, + className?: string }; type State = { @@ -55,12 +56,12 @@ export default class UsersSearch extends React.PureComponent { render() { const { query } = this.state; + const searchBoxClass = classNames('search-box', this.props.className); const inputClassName = classNames('search-box-input', { touched: query != null && query.length === 1 }); return ( - <div className="panel panel-vertical bordered-bottom spacer-bottom"> - <div className="search-box"> + <div className={searchBoxClass}> <button className="search-box-submit button-clean"> <i className="icon-search" /> </button> @@ -76,7 +77,6 @@ export default class UsersSearch extends React.PureComponent { {translateWithParameters('select2.tooShort', 2)} </span> </div> - </div> ); } } diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js index 5026720f2b2..4fdecd3935e 100644 --- a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js +++ b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js @@ -21,6 +21,7 @@ import React from 'react'; import Select from 'react-select'; import { debounce } from 'lodash'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; import UsersSelectSearchOption from './UsersSelectSearchOption'; import UsersSelectSearchValue from './UsersSelectSearchValue'; import './UsersSelectSearch.css'; @@ -54,7 +55,7 @@ export default class UsersSelectSearch extends React.PureComponent { constructor(props: Props) { super(props); - this.handleSearch = debounce(this.handleSearch); + this.handleSearch = debounce(this.handleSearch, 250); this.state = { searchResult: [], isLoading: false, search: '' }; } @@ -73,7 +74,8 @@ export default class UsersSelectSearch extends React.PureComponent { handleSearch = (search: string) => { this.setState({ isLoading: true, search }); - this.props.searchUsers(search, Math.min(this.props.excludedUsers.length + LIST_SIZE, 500)) + this.props + .searchUsers(search, Math.min(this.props.excludedUsers.length + LIST_SIZE, 500)) .then(this.filterSearchResult) .then(searchResult => { this.setState({ isLoading: false, searchResult }); @@ -81,6 +83,9 @@ export default class UsersSelectSearch extends React.PureComponent { }; render() { + const noResult = this.state.search.length === 1 + ? translateWithParameters('select2.tooShort', 2) + : translate('no_results'); return ( <Select className="Select-big" @@ -92,6 +97,7 @@ export default class UsersSelectSearch extends React.PureComponent { onInputChange={this.handleSearch} value={this.props.selectedUser} placeholder="" + noResultsText={noResult} labelKey="name" valueKey="login" clearable={false} diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/UserSearch-test.js b/server/sonar-web/src/main/js/apps/users/components/__tests__/UserSearch-test.js index 33a4c3c9b86..71fd3cc465e 100644 --- a/server/sonar-web/src/main/js/apps/users/components/__tests__/UserSearch-test.js +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/UserSearch-test.js @@ -21,21 +21,15 @@ import React from 'react'; import { shallow } from 'enzyme'; import UsersSearch from '../UsersSearch'; -const onSearch = jest.fn(); - -it('should render correctly without any search query', () => { - const wrapper = shallow(<UsersSearch onSearch={onSearch} query={null} />); +it('should render correctly', () => { + const wrapper = shallow(<UsersSearch onSearch={jest.fn()} className="test" />); expect(wrapper).toMatchSnapshot(); -}); - -it('should render with a search query', () => { - const wrapper = shallow(<UsersSearch onSearch={onSearch} query={'foo'} />); + wrapper.setState({ query: 'foo' }); expect(wrapper).toMatchSnapshot(); }); it('should display a help message when there is less than 2 characters', () => { - const wrapper = shallow(<UsersSearch onSearch={onSearch} query={'a'} />); - expect(wrapper).toMatchSnapshot(); - wrapper.setState({ query: 'foo' }); + const wrapper = shallow(<UsersSearch onSearch={jest.fn()} />); + wrapper.setState({ query: 'f' }); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserSearch-test.js.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserSearch-test.js.snap index edc0fee72b3..5bac91a7a55 100644 --- a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserSearch-test.js.snap +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserSearch-test.js.snap @@ -1,99 +1,65 @@ exports[`test should display a help message when there is less than 2 characters 1`] = ` <div - className="panel panel-vertical bordered-bottom spacer-bottom"> - <div - className="search-box"> - <button - className="search-box-submit button-clean"> - <i - className="icon-search" /> - </button> - <input - autoComplete="off" - className="search-box-input" - onChange={[Function]} - placeholder="search_verb" - type="search" - value="" /> - <span - className="note spacer-left text-middle"> - select2.tooShort.2 - </span> - </div> + className="search-box"> + <button + className="search-box-submit button-clean"> + <i + className="icon-search" /> + </button> + <input + autoComplete="off" + className="search-box-input touched" + onChange={[Function]} + placeholder="search_verb" + type="search" + value="f" /> + <span + className="note spacer-left text-middle"> + select2.tooShort.2 + </span> </div> `; -exports[`test should display a help message when there is less than 2 characters 2`] = ` +exports[`test should render correctly 1`] = ` <div - className="panel panel-vertical bordered-bottom spacer-bottom"> - <div - className="search-box"> - <button - className="search-box-submit button-clean"> - <i - className="icon-search" /> - </button> - <input - autoComplete="off" - className="search-box-input" - onChange={[Function]} - placeholder="search_verb" - type="search" - value="foo" /> - <span - className="note spacer-left text-middle"> - select2.tooShort.2 - </span> - </div> + className="search-box test"> + <button + className="search-box-submit button-clean"> + <i + className="icon-search" /> + </button> + <input + autoComplete="off" + className="search-box-input" + onChange={[Function]} + placeholder="search_verb" + type="search" + value="" /> + <span + className="note spacer-left text-middle"> + select2.tooShort.2 + </span> </div> `; -exports[`test should render correctly without any search query 1`] = ` +exports[`test should render correctly 2`] = ` <div - className="panel panel-vertical bordered-bottom spacer-bottom"> - <div - className="search-box"> - <button - className="search-box-submit button-clean"> - <i - className="icon-search" /> - </button> - <input - autoComplete="off" - className="search-box-input" - onChange={[Function]} - placeholder="search_verb" - type="search" - value="" /> - <span - className="note spacer-left text-middle"> - select2.tooShort.2 - </span> - </div> -</div> -`; - -exports[`test should render with a search query 1`] = ` -<div - className="panel panel-vertical bordered-bottom spacer-bottom"> - <div - className="search-box"> - <button - className="search-box-submit button-clean"> - <i - className="icon-search" /> - </button> - <input - autoComplete="off" - className="search-box-input" - onChange={[Function]} - placeholder="search_verb" - type="search" - value="" /> - <span - className="note spacer-left text-middle"> - select2.tooShort.2 - </span> - </div> + className="search-box test"> + <button + className="search-box-submit button-clean"> + <i + className="icon-search" /> + </button> + <input + autoComplete="off" + className="search-box-input" + onChange={[Function]} + placeholder="search_verb" + type="search" + value="foo" /> + <span + className="note spacer-left text-middle"> + select2.tooShort.2 + </span> </div> `; diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.js.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.js.snap index e4f6d80595e..7ab91b1de70 100644 --- a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.js.snap +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.js.snap @@ -24,7 +24,7 @@ exports[`test should render correctly 1`] = ` menuBuffer={0} menuRenderer={[Function]} multi={false} - noResultsText="No results found" + noResultsText="no_results" onBlurResetsInput={true} onChange={[Function]} onCloseResetsInput={true} @@ -91,7 +91,7 @@ exports[`test should render correctly 3`] = ` menuBuffer={0} menuRenderer={[Function]} multi={false} - noResultsText="No results found" + noResultsText="no_results" onBlurResetsInput={true} onChange={[Function]} onCloseResetsInput={true} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js index 8d5f42cb719..d663e9162ca 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js @@ -22,8 +22,6 @@ import React from 'react'; import ListFooter from '../ListFooter'; import { click } from '../../../helpers/testUtils'; -const loadMore = jest.fn(); - it('should render "3 of 5 shown"', () => { const listFooter = shallow(<ListFooter count={3} total={5} />); expect(listFooter.text()).toContain('x_of_y_shown.3.5'); @@ -35,11 +33,12 @@ it('should not render "show more"', () => { }); it('should not render "show more"', () => { - const listFooter = shallow(<ListFooter count={5} total={5} loadMore={loadMore} />); + const listFooter = shallow(<ListFooter count={5} total={5} loadMore={jest.fn()} />); expect(listFooter.find('a').length).toBe(0); }); it('should "show more"', () => { + const loadMore = jest.fn(); const listFooter = shallow(<ListFooter count={3} total={5} loadMore={loadMore} />); const link = listFooter.find('a'); expect(link.length).toBe(1); diff --git a/server/sonar-web/src/main/less/components/page.less b/server/sonar-web/src/main/less/components/page.less index ec4df716077..9e24c26252f 100644 --- a/server/sonar-web/src/main/less/components/page.less +++ b/server/sonar-web/src/main/less/components/page.less @@ -84,12 +84,6 @@ top: 3px; margin-left: 8px; } - - .page-totalcount { - position: absolute; - bottom: -50px; - right: 0; - } } .page-title { diff --git a/server/sonar-web/src/main/less/init/lists.less b/server/sonar-web/src/main/less/init/lists.less index f62499995ac..193e2f2d301 100644 --- a/server/sonar-web/src/main/less/init/lists.less +++ b/server/sonar-web/src/main/less/init/lists.less @@ -63,6 +63,14 @@ ol, ul { } } +.list-item-checkable-link { + cursor: pointer; + + &:focus { + outline: none; + } +} + // Definition lists |