aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/groups
diff options
context:
space:
mode:
authorguillaume-peoch-sonarsource <guillaume.peoch@sonarsource.com>2023-03-14 12:30:42 +0100
committersonartech <sonartech@sonarsource.com>2023-03-22 20:04:08 +0000
commit37add481610d383abdac8982537c8d6370463b84 (patch)
tree65b38ca944e63bd450cd15d454a04646d715bf60 /server/sonar-web/src/main/js/apps/groups
parent0de4d1230e46f1ab72df252c1f17ec3fd0796132 (diff)
downloadsonarqube-37add481610d383abdac8982537c8d6370463b84.tar.gz
sonarqube-37add481610d383abdac8982537c8d6370463b84.zip
SONAR-18657 Add Filters for all, local and managed groups on groups list
Diffstat (limited to 'server/sonar-web/src/main/js/apps/groups')
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/App.tsx133
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/Header.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/List.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx62
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx191
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/DeleteForm-test.tsx29
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembersModal-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx49
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/GroupsApp-it.tsx255
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx35
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/List-test.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/ListItem-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap94
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/DeleteForm-test.tsx.snap45
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap14
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap82
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap69
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/ListItem-test.tsx.snap5
21 files changed, 413 insertions, 689 deletions
diff --git a/server/sonar-web/src/main/js/apps/groups/components/App.tsx b/server/sonar-web/src/main/js/apps/groups/components/App.tsx
index 31cd060f6c9..847d29b9fb1 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/components/App.tsx
@@ -22,6 +22,7 @@ import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { getSystemInfo } from '../../../api/system';
import { createGroup, deleteGroup, searchUsersGroups, updateGroup } from '../../../api/user_groups';
+import ButtonToggle from '../../../components/controls/ButtonToggle';
import ListFooter from '../../../components/controls/ListFooter';
import SearchBox from '../../../components/controls/SearchBox';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
@@ -41,11 +42,17 @@ interface State {
paging?: Paging;
query: string;
manageProvider?: string;
+ managed: boolean | undefined;
}
export default class App extends React.PureComponent<{}, State> {
mounted = false;
- state: State = { loading: true, query: '' };
+ state: State = {
+ loading: true,
+ query: '',
+ managed: undefined,
+ paging: { pageIndex: 1, pageSize: 100, total: 1000 },
+ };
componentDidMount() {
this.mounted = true;
@@ -53,18 +60,19 @@ export default class App extends React.PureComponent<{}, State> {
this.fetchManageInstance();
}
+ componentDidUpdate(_prevProps: {}, prevState: State) {
+ if (prevState.query !== this.state.query || prevState.managed !== this.state.managed) {
+ this.fetchGroups();
+ }
+ if (prevState !== undefined && prevState.paging?.pageIndex !== this.state.paging?.pageIndex) {
+ this.fetchMoreGroups();
+ }
+ }
+
componentWillUnmount() {
this.mounted = false;
}
- makeFetchGroupsRequest = (data?: { p?: number; q?: string }) => {
- this.setState({ loading: true });
- return searchUsersGroups({
- q: this.state.query,
- ...data,
- });
- };
-
async fetchManageInstance() {
const info = (await getSystemInfo()) as SysInfoCluster;
if (this.mounted) {
@@ -80,9 +88,14 @@ export default class App extends React.PureComponent<{}, State> {
}
};
- fetchGroups = async (data?: { p?: number; q?: string }) => {
+ fetchGroups = async () => {
+ const { query: q, managed } = this.state;
+ this.setState({ loading: true });
try {
- const { groups, paging } = await this.makeFetchGroupsRequest(data);
+ const { groups, paging } = await searchUsersGroups({
+ q,
+ managed,
+ });
if (this.mounted) {
this.setState({ groups, loading: false, paging });
}
@@ -92,11 +105,13 @@ export default class App extends React.PureComponent<{}, State> {
};
fetchMoreGroups = async () => {
- const { paging: currentPaging } = this.state;
+ const { query: q, managed, paging: currentPaging } = this.state;
if (currentPaging && currentPaging.total > currentPaging.pageIndex * currentPaging.pageSize) {
try {
- const { groups, paging } = await this.makeFetchGroupsRequest({
- p: currentPaging.pageIndex + 1,
+ const { groups, paging } = await searchUsersGroups({
+ p: currentPaging.pageIndex,
+ q,
+ managed,
});
if (this.mounted) {
this.setState(({ groups: existingGroups = [] }) => ({
@@ -111,15 +126,10 @@ export default class App extends React.PureComponent<{}, State> {
}
};
- search = (query: string) => {
- this.fetchGroups({ q: query });
- this.setState({ query });
- };
-
refresh = async () => {
- const { paging, query } = this.state;
+ const { paging } = this.state;
- await this.fetchGroups({ q: query });
+ await this.fetchGroups();
// reload all pages in order
if (paging && paging.pageIndex > 1) {
@@ -130,22 +140,6 @@ export default class App extends React.PureComponent<{}, State> {
}
};
- closeDeleteForm = () => {
- this.setState({ groupToBeDeleted: undefined });
- };
-
- closeEditForm = () => {
- this.setState({ editedGroup: undefined });
- };
-
- openDeleteForm = (group: Group) => {
- this.setState({ groupToBeDeleted: group });
- };
-
- openEditForm = (group: Group) => {
- this.setState({ editedGroup: group });
- };
-
handleCreate = async (data: { description: string; name: string }) => {
await createGroup({ ...data });
@@ -200,8 +194,16 @@ export default class App extends React.PureComponent<{}, State> {
};
render() {
- const { editedGroup, groupToBeDeleted, groups, loading, paging, query, manageProvider } =
- this.state;
+ const {
+ editedGroup,
+ groupToBeDeleted,
+ groups,
+ loading,
+ paging,
+ query,
+ manageProvider,
+ managed,
+ } = this.state;
const showAnyone = 'anyone'.includes(query.toLowerCase());
@@ -212,22 +214,45 @@ export default class App extends React.PureComponent<{}, State> {
<main className="page page-limited" id="groups-page">
<Header onCreate={this.handleCreate} manageProvider={manageProvider} />
- <SearchBox
- className="big-spacer-bottom"
- id="groups-search"
- minLength={2}
- onChange={this.search}
- placeholder={translate('search.search_by_name')}
- value={query}
- />
+ <div className="display-flex-justify-start big-spacer-bottom big-spacer-top">
+ {manageProvider !== undefined && (
+ <div className="big-spacer-right">
+ <ButtonToggle
+ value={managed === undefined ? 'all' : managed}
+ disabled={loading}
+ options={[
+ { label: translate('all'), value: 'all' },
+ { label: translate('managed'), value: true },
+ { label: translate('local'), value: false },
+ ]}
+ onCheck={(filterOption) => {
+ if (filterOption === 'all') {
+ this.setState({ managed: undefined });
+ } else {
+ this.setState({ managed: filterOption as boolean });
+ }
+ }}
+ />
+ </div>
+ )}
+ <SearchBox
+ className="big-spacer-bottom"
+ id="groups-search"
+ minLength={2}
+ onChange={(q) => this.setState({ query: q })}
+ placeholder={translate('search.search_by_name')}
+ value={query}
+ />
+ </div>
{groups !== undefined && (
<List
groups={groups}
- onDelete={this.openDeleteForm}
- onEdit={this.openEditForm}
+ onDelete={(groupToBeDeleted) => this.setState({ groupToBeDeleted })}
+ onEdit={(editedGroup) => this.setState({ editedGroup })}
onEditMembers={this.refresh}
showAnyone={showAnyone}
+ manageProvider={manageProvider}
/>
)}
@@ -236,7 +261,11 @@ export default class App extends React.PureComponent<{}, State> {
<ListFooter
count={showAnyone ? groups.length + 1 : groups.length}
loading={loading}
- loadMore={this.fetchMoreGroups}
+ loadMore={() => {
+ if (paging.total > paging.pageIndex * paging.pageSize) {
+ this.setState({ paging: { ...paging, pageIndex: paging.pageIndex + 1 } });
+ }
+ }}
ready={!loading}
total={showAnyone ? paging.total + 1 : paging.total}
/>
@@ -246,7 +275,7 @@ export default class App extends React.PureComponent<{}, State> {
{groupToBeDeleted && (
<DeleteForm
group={groupToBeDeleted}
- onClose={this.closeDeleteForm}
+ onClose={() => this.setState({ groupToBeDeleted: undefined })}
onSubmit={this.handleDelete}
/>
)}
@@ -256,7 +285,7 @@ export default class App extends React.PureComponent<{}, State> {
confirmButtonText={translate('update_verb')}
group={editedGroup}
header={translate('groups.update_group')}
- onClose={this.closeEditForm}
+ onClose={() => this.setState({ editedGroup: undefined })}
onSubmit={this.handleEdit}
/>
)}
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 d22a55ee014..ca86ea9fa52 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
@@ -20,7 +20,7 @@
import * as React from 'react';
import { ButtonIcon } from '../../../components/controls/buttons';
import BulletListIcon from '../../../components/icons/BulletListIcon';
-import { translate } from '../../../helpers/l10n';
+import { translateWithParameters } from '../../../helpers/l10n';
import { Group } from '../../../types/types';
import EditMembersModal from './EditMembersModal';
@@ -61,10 +61,10 @@ export default class EditMembers extends React.PureComponent<Props, State> {
return (
<>
<ButtonIcon
- aria-label={translate('groups.users.edit')}
+ aria-label={translateWithParameters('groups.users.edit', this.props.group.name)}
className="button-small"
onClick={this.handleMembersClick}
- title={translate('groups.users.edit')}
+ title={translateWithParameters('groups.users.edit', this.props.group.name)}
>
<BulletListIcon />
</ButtonIcon>
diff --git a/server/sonar-web/src/main/js/apps/groups/components/Header.tsx b/server/sonar-web/src/main/js/apps/groups/components/Header.tsx
index 81e71d6cd8d..8a681c42b5e 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/Header.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/components/Header.tsx
@@ -25,12 +25,12 @@ import { Alert } from '../../../components/ui/Alert';
import { translate } from '../../../helpers/l10n';
import Form from './Form';
-interface Props {
+interface HeaderProps {
onCreate: (data: { description: string; name: string }) => Promise<void>;
manageProvider?: string;
}
-export default function Header(props: Props) {
+export default function Header(props: HeaderProps) {
const { manageProvider } = props;
const [createModal, setCreateModal] = React.useState(false);
diff --git a/server/sonar-web/src/main/js/apps/groups/components/List.tsx b/server/sonar-web/src/main/js/apps/groups/components/List.tsx
index 74bd3440f5e..06b658e5724 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/List.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/components/List.tsx
@@ -29,9 +29,12 @@ interface Props {
onEdit: (group: Group) => void;
onEditMembers: () => void;
showAnyone: boolean;
+ manageProvider: string | undefined;
}
export default function List(props: Props) {
+ const { groups, manageProvider, showAnyone } = props;
+
return (
<div className="boxed-group boxed-group-inner">
<table className="data zebra zebra-hover" id="groups-list">
@@ -46,7 +49,7 @@ export default function List(props: Props) {
</tr>
</thead>
<tbody>
- {props.showAnyone && (
+ {showAnyone && (
<tr className="js-anyone" key="anyone">
<td className="width-20">
<strong className="js-group-name">{translate('groups.anyone')}</strong>
@@ -61,13 +64,14 @@ export default function List(props: Props) {
</tr>
)}
- {sortBy(props.groups, (group) => group.name.toLowerCase()).map((group) => (
+ {sortBy(groups, (group) => group.name.toLowerCase()).map((group) => (
<ListItem
group={group}
key={group.name}
onDelete={props.onDelete}
onEdit={props.onEdit}
onEditMembers={props.onEditMembers}
+ manageProvider={manageProvider}
/>
))}
</tbody>
diff --git a/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx b/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx
index f148746e94a..c631103f75a 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx
@@ -22,7 +22,7 @@ import ActionsDropdown, {
ActionsDropdownDivider,
ActionsDropdownItem,
} from '../../../components/controls/ActionsDropdown';
-import { translate } from '../../../helpers/l10n';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Group } from '../../../types/types';
import EditMembers from './EditMembers';
@@ -31,41 +31,63 @@ export interface ListItemProps {
onDelete: (group: Group) => void;
onEdit: (group: Group) => void;
onEditMembers: () => void;
+ manageProvider: string | undefined;
}
export default function ListItem(props: ListItemProps) {
- const { group } = props;
+ const { manageProvider, group } = props;
+ const { name, managed, membersCount, description } = group;
+
+ const isManaged = () => {
+ return manageProvider !== undefined;
+ };
+
+ const isGroupLocal = () => {
+ return isManaged() && !managed;
+ };
return (
- <tr data-id={group.name}>
+ <tr data-id={name}>
<td className="width-20">
- <strong className="js-group-name">{group.name}</strong>
+ <strong className="js-group-name">{name}</strong>
{group.default && <span className="little-spacer-left">({translate('default')})</span>}
+ {isGroupLocal() && <span className="little-spacer-left badge">{translate('local')}</span>}
</td>
- <td className="thin text-middle text-right little-padded-right">{group.membersCount}</td>
+ <td className="thin text-middle text-right little-padded-right">{membersCount}</td>
<td className="little-padded-left">
- {!group.default && <EditMembers group={group} onEdit={props.onEditMembers} />}
+ {!group.default && !isManaged() && (
+ <EditMembers group={group} onEdit={props.onEditMembers} />
+ )}
</td>
<td className="width-40">
- <span className="js-group-description">{group.description}</span>
+ <span className="js-group-description">{description}</span>
</td>
<td className="thin nowrap text-right">
- {!group.default && (
- <ActionsDropdown>
- <ActionsDropdownItem className="js-group-update" onClick={() => props.onEdit(group)}>
- {translate('update_details')}
- </ActionsDropdownItem>
- <ActionsDropdownDivider />
- <ActionsDropdownItem
- className="js-group-delete"
- destructive={true}
- onClick={() => props.onDelete(group)}
- >
- {translate('delete')}
- </ActionsDropdownItem>
+ {!group.default && (!isManaged() || isGroupLocal()) && (
+ <ActionsDropdown label={translateWithParameters('groups.edit', group.name)}>
+ {!isManaged() && (
+ <>
+ <ActionsDropdownItem
+ className="js-group-update"
+ onClick={() => props.onEdit(group)}
+ >
+ {translate('update_details')}
+ </ActionsDropdownItem>
+ <ActionsDropdownDivider />
+ </>
+ )}
+ {(!isManaged() || isGroupLocal()) && (
+ <ActionsDropdownItem
+ className="js-group-delete"
+ destructive={true}
+ onClick={() => props.onDelete(group)}
+ >
+ {translate('delete')}
+ </ActionsDropdownItem>
+ )}
</ActionsDropdown>
)}
</td>
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx
deleted file mode 100644
index 8858be2fd5e..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import {
- createGroup,
- deleteGroup,
- searchUsersGroups,
- updateGroup,
-} from '../../../../api/user_groups';
-import { mockGroup } from '../../../../helpers/testMocks';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
-import App from '../App';
-
-jest.mock('../../../../api/user_groups', () => ({
- createGroup: jest.fn().mockResolvedValue({
- default: false,
- description: 'Desc foo',
- membersCount: 0,
- name: 'Foo',
- }),
- deleteGroup: jest.fn().mockResolvedValue({}),
- searchUsersGroups: jest.fn().mockResolvedValue({
- paging: { pageIndex: 1, pageSize: 2, total: 4 },
- groups: [
- {
- default: false,
- description: 'Owners of organization foo',
- membersCount: 1,
- name: 'Owners',
- },
- {
- default: true,
- description: 'Members of organization foo',
- membersCount: 2,
- name: 'Members',
- },
- ],
- }),
- updateGroup: jest.fn().mockResolvedValue({}),
-}));
-
-jest.mock('../../../../api/system', () => ({
- getSystemInfo: jest.fn().mockResolvedValue({ System: {} }),
-}));
-
-beforeEach(() => {
- jest.clearAllMocks();
-});
-
-it('should render correctly', async () => {
- const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
- await waitAndUpdate(wrapper);
- expect(searchUsersGroups).toHaveBeenCalledWith({ q: '' });
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should correctly handle creation', async () => {
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- expect(wrapper.state('groups')).toHaveLength(2);
- wrapper.instance().handleCreate({ description: 'Desc foo', name: 'foo' });
- await waitAndUpdate(wrapper);
- expect(createGroup).toHaveBeenCalled();
-});
-
-it('should correctly handle deletion', async () => {
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- expect(wrapper.state('groups')).toHaveLength(2);
- wrapper.setState({ groupToBeDeleted: mockGroup({ name: 'Members' }) });
- wrapper.instance().handleDelete();
- await waitAndUpdate(wrapper);
- expect(deleteGroup).toHaveBeenCalled();
- expect(wrapper.state().groupToBeDeleted).toBeUndefined();
-});
-
-it('should ignore deletion', async () => {
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- wrapper.setState({ groupToBeDeleted: undefined });
- wrapper.instance().handleDelete();
- expect(deleteGroup).not.toHaveBeenCalled();
-});
-
-it('should correctly handle edition', async () => {
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- wrapper.setState({ editedGroup: mockGroup({ name: 'Owners' }) });
- wrapper.instance().handleEdit({ description: 'foo', name: 'bar' });
- await waitAndUpdate(wrapper);
- expect(updateGroup).toHaveBeenCalled();
- expect(wrapper.state('groups')).toContainEqual({
- default: false,
- description: 'foo',
- membersCount: 1,
- name: 'bar',
- });
-});
-
-it('should ignore edition', async () => {
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- wrapper.setState({ editedGroup: undefined });
- wrapper.instance().handleEdit({ description: 'nope', name: 'nuhuh' });
- expect(updateGroup).not.toHaveBeenCalled();
-});
-
-it('should fetch more groups', async () => {
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- wrapper.find('ListFooter').prop<Function>('loadMore')();
- await waitAndUpdate(wrapper);
- expect(searchUsersGroups).toHaveBeenCalledWith({ p: 2, q: '' });
- expect(wrapper.state('groups')).toHaveLength(4);
-});
-
-it('should search for groups', async () => {
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- wrapper.find('SearchBox').prop<Function>('onChange')('foo');
- expect(searchUsersGroups).toHaveBeenCalledWith({ q: 'foo' });
- expect(wrapper.state('query')).toBe('foo');
-});
-
-it('should handle edit modal', async () => {
- const editedGroup = mockGroup();
-
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- expect(wrapper.state().editedGroup).toBeUndefined();
-
- wrapper.instance().openEditForm(editedGroup);
- expect(wrapper.state().editedGroup).toEqual(editedGroup);
-
- wrapper.instance().closeEditForm();
- expect(wrapper.state().editedGroup).toBeUndefined();
-});
-
-it('should handle delete modal', async () => {
- const groupToBeDeleted = mockGroup();
-
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- expect(wrapper.state().groupToBeDeleted).toBeUndefined();
-
- wrapper.instance().openDeleteForm(groupToBeDeleted);
- expect(wrapper.state().groupToBeDeleted).toEqual(groupToBeDeleted);
-
- wrapper.instance().closeDeleteForm();
- expect(wrapper.state().groupToBeDeleted).toBeUndefined();
-});
-
-it('should refresh correctly', async () => {
- const wrapper = shallowRender();
-
- await waitAndUpdate(wrapper);
-
- const query = 'preserve me';
- wrapper.setState({ paging: { pageIndex: 2, pageSize: 2, total: 5 }, query });
-
- (searchUsersGroups as jest.Mock).mockClear();
-
- wrapper.instance().refresh();
- await waitAndUpdate(wrapper);
-
- expect(searchUsersGroups).toHaveBeenNthCalledWith(1, { q: query });
- expect(searchUsersGroups).toHaveBeenNthCalledWith(2, { q: query, p: 2 });
-});
-
-function shallowRender(props: Partial<App['props']> = {}) {
- return shallow<App>(<App {...props} />);
-}
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/DeleteForm-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/DeleteForm-test.tsx
deleted file mode 100644
index 0ae92dc74c6..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/DeleteForm-test.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import DeleteForm from '../DeleteForm';
-
-it('should render', () => {
- const group = { id: 3, name: 'Foo', membersCount: 5 };
- expect(
- shallow(<DeleteForm group={group} onClose={jest.fn()} onSubmit={jest.fn()} />).dive()
- ).toMatchSnapshot();
-});
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 5c0e6a440b5..cce45974640 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
@@ -19,11 +19,12 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockGroup } from '../../../../helpers/testMocks';
import { click } from '../../../../helpers/testUtils';
import EditMembers from '../EditMembers';
it('should edit members', () => {
- const group = { id: 3, name: 'Foo', membersCount: 5 };
+ const group = mockGroup({ name: 'Foo', membersCount: 5 });
const onEdit = jest.fn();
const wrapper = shallow(<EditMembers group={group} onEdit={onEdit} />);
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
index a8b4cc49e0f..d0ba4a37412 100644
--- 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
@@ -21,10 +21,11 @@ import { shallow } from 'enzyme';
import * as React from 'react';
import { addUserToGroup, getUsersInGroup, removeUserFromGroup } from '../../../../api/user_groups';
import SelectList, { SelectListFilter } from '../../../../components/controls/SelectList';
+import { mockGroup } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import EditMembersModal from '../EditMembersModal';
-const group = { id: 1, name: 'foo', membersCount: 1 };
+const group = mockGroup({ name: 'foo', membersCount: 1 });
jest.mock('../../../../api/user_groups', () => ({
getUsersInGroup: jest.fn().mockResolvedValue({
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx
deleted file mode 100644
index 31992d1d140..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import { change, click, submit } from '../../../../helpers/testUtils';
-import Form from '../Form';
-
-it('should render form', async () => {
- const onClose = jest.fn();
- const onSubmit = jest.fn(() => Promise.resolve());
- const wrapper = shallow(
- <Form
- confirmButtonText="confirmButtonText"
- header="header"
- onClose={onClose}
- onSubmit={onSubmit}
- />
- ).dive();
- expect(wrapper).toMatchSnapshot();
-
- change(wrapper.find('[name="name"]'), 'foo');
- change(wrapper.find('[name="description"]'), 'bar');
- submit(wrapper.find('form'));
- expect(onSubmit).toHaveBeenCalledWith({ description: 'bar', name: 'foo' });
-
- await new Promise(setImmediate);
- expect(onClose).toHaveBeenCalled();
-
- onClose.mockClear();
- click(wrapper.find('ResetButtonLink'));
- expect(onClose).toHaveBeenCalled();
-});
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/GroupsApp-it.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/GroupsApp-it.tsx
new file mode 100644
index 00000000000..d705163afe5
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/GroupsApp-it.tsx
@@ -0,0 +1,255 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import * as React from 'react';
+import { act } from 'react-dom/test-utils';
+import { byRole, byText } from 'testing-library-selector';
+import GroupsServiceMock from '../../../../api/mocks/GroupsServiceMock';
+import { renderApp } from '../../../../helpers/testReactTestingUtils';
+import App from '../App';
+
+jest.mock('../../../../api/users');
+jest.mock('../../../../api/system');
+jest.mock('../../../../api/user_groups');
+
+const handler = new GroupsServiceMock();
+
+const ui = {
+ createGroupButton: byRole('button', { name: 'groups.create_group' }),
+ infoManageMode: byText(/groups\.page\.managed_description/),
+ description: byText('user_groups.page.description'),
+ allFilter: byRole('button', { name: 'all' }),
+ managedFilter: byRole('button', { name: 'managed' }),
+ localFilter: byRole('button', { name: 'local' }),
+ searchInput: byRole('searchbox', { name: 'search.search_by_name' }),
+ updateButton: byRole('button', { name: 'update_details' }),
+ updateDialog: byRole('dialog', { name: 'groups.update_group' }),
+ updateDialogButton: byRole('button', { name: 'update_verb' }),
+ deleteButton: byRole('button', { name: 'delete' }),
+ deleteDialog: byRole('dialog', { name: 'groups.delete_group' }),
+ deleteDialogButton: byRole('button', { name: 'delete' }),
+ showMore: byRole('button', { name: 'show_more' }),
+ nameInput: byRole('textbox', { name: 'name field_required' }),
+ descriptionInput: byRole('textbox', { name: 'description' }),
+ createGroupDialogButton: byRole('button', { name: 'create' }),
+ editGroupDialogButton: byRole('button', { name: 'groups.create_group' }),
+
+ createGroupDialog: byRole('dialog', { name: 'groups.create_group' }),
+ membersDialog: byRole('dialog', { name: 'users.update' }),
+
+ managedGroupRow: byRole('row', { name: 'managed-group 1' }),
+ managedGroupEditMembersButton: byRole('button', { name: 'groups.users.edit.managed-group' }),
+ managedEditButton: byRole('button', { name: 'groups.edit.managed-group' }),
+
+ localGroupRow: byRole('row', { name: 'local-group 1' }),
+ localGroupEditMembersButton: byRole('button', { name: 'groups.users.edit.local-group' }),
+ localGroupRow2: byRole('row', { name: 'local-group 2 1 group 2 is loco!' }),
+ editedLocalGroupRow: byRole('row', { name: 'local-group 3 1 group 3 rocks!' }),
+ localEditButton: byRole('button', { name: 'groups.edit.local-group' }),
+ localGroupRowWithLocalBadge: byRole('row', {
+ name: 'local-group local 1',
+ }),
+};
+
+describe('in non managed mode', () => {
+ beforeEach(() => {
+ handler.setIsManaged(false);
+ handler.reset();
+ });
+
+ it('should render all groups', async () => {
+ renderGroupsApp();
+
+ expect(await ui.localGroupRow.find()).toBeInTheDocument();
+ expect(ui.managedGroupRow.get()).toBeInTheDocument();
+ expect(ui.localGroupRowWithLocalBadge.query()).not.toBeInTheDocument();
+ });
+
+ it('should be able to create a group', async () => {
+ const user = userEvent.setup();
+ renderGroupsApp();
+
+ expect(await ui.description.find()).toBeInTheDocument();
+
+ await user.click(ui.createGroupButton.get());
+ expect(ui.createGroupDialog.get()).toBeInTheDocument();
+
+ await user.type(ui.nameInput.get(), 'local-group 2');
+ await user.type(ui.descriptionInput.get(), 'group 2 is loco!');
+
+ await act(async () => {
+ await user.click(ui.createGroupDialogButton.get());
+ });
+
+ expect(await ui.localGroupRow2.find()).toBeInTheDocument();
+ });
+
+ it('should be able to delete a group', async () => {
+ const user = userEvent.setup();
+ renderGroupsApp();
+
+ await user.click(await ui.localEditButton.find());
+ await user.click(await ui.deleteButton.find());
+
+ expect(await ui.deleteDialog.find()).toBeInTheDocument();
+ await act(async () => {
+ await user.click(ui.deleteDialogButton.get());
+ });
+
+ expect(await ui.managedGroupRow.find()).toBeInTheDocument();
+ expect(ui.localGroupRow.query()).not.toBeInTheDocument();
+ });
+
+ it('should be able to edit a group', async () => {
+ const user = userEvent.setup();
+ renderGroupsApp();
+
+ await user.click(await ui.localEditButton.find());
+ await user.click(await ui.updateButton.find());
+
+ expect(ui.updateDialog.get()).toBeInTheDocument();
+
+ await user.clear(ui.nameInput.get());
+ await user.type(ui.nameInput.get(), 'local-group 3');
+ await user.clear(ui.descriptionInput.get());
+ await user.type(ui.descriptionInput.get(), 'group 3 rocks!');
+
+ expect(ui.updateDialog.get()).toBeInTheDocument();
+
+ await act(async () => {
+ await user.click(ui.updateDialogButton.get());
+ });
+
+ expect(await ui.managedGroupRow.find()).toBeInTheDocument();
+ expect(await ui.editedLocalGroupRow.find()).toBeInTheDocument();
+ });
+
+ it('should be able to edit the members of a group', async () => {
+ const user = userEvent.setup();
+ renderGroupsApp();
+
+ expect(await ui.localGroupRow.find()).toBeInTheDocument();
+ expect(await ui.localGroupEditMembersButton.find()).toBeInTheDocument();
+
+ await user.click(ui.localGroupEditMembersButton.get());
+ expect(await ui.membersDialog.find()).toBeInTheDocument();
+ });
+
+ it('should be able search a group', async () => {
+ const user = userEvent.setup();
+ renderGroupsApp();
+
+ expect(await ui.localGroupRow.find()).toBeInTheDocument();
+ expect(ui.managedGroupRow.get()).toBeInTheDocument();
+
+ await user.type(await ui.searchInput.find(), 'local');
+
+ expect(await ui.localGroupRow.find()).toBeInTheDocument();
+ expect(ui.managedGroupRow.query()).not.toBeInTheDocument();
+ });
+
+ it('should be able load more group', async () => {
+ const user = userEvent.setup();
+ renderGroupsApp();
+
+ // including the anyone (deprecated) group
+ expect(await screen.findAllByRole('row')).toHaveLength(4);
+
+ await user.click(await ui.showMore.find());
+
+ expect(await screen.findAllByRole('row')).toHaveLength(6);
+ });
+});
+
+describe('in manage mode', () => {
+ beforeEach(() => {
+ handler.setIsManaged(true);
+ handler.reset();
+ });
+
+ it('should not be able to create a group', async () => {
+ renderGroupsApp();
+ expect(await ui.createGroupButton.find()).toBeDisabled();
+ expect(ui.infoManageMode.get()).toBeInTheDocument();
+ });
+
+ it('should ONLY be able to delete a local group', async () => {
+ const user = userEvent.setup();
+ renderGroupsApp();
+
+ expect(await ui.localGroupRowWithLocalBadge.find()).toBeInTheDocument();
+
+ await user.click(await ui.localFilter.find());
+ await user.click(await ui.localEditButton.find());
+ expect(ui.updateButton.query()).not.toBeInTheDocument();
+
+ await user.click(await ui.deleteButton.find());
+
+ expect(await ui.deleteDialog.find()).toBeInTheDocument();
+ await act(async () => {
+ await user.click(ui.deleteDialogButton.get());
+ });
+ expect(ui.localGroupRowWithLocalBadge.query()).not.toBeInTheDocument();
+ });
+
+ it('should not be able to delete or edit a managed group', async () => {
+ renderGroupsApp();
+
+ expect(await ui.managedGroupRow.find()).toBeInTheDocument();
+ expect(ui.managedEditButton.query()).not.toBeInTheDocument();
+
+ expect(ui.managedGroupEditMembersButton.query()).not.toBeInTheDocument();
+ });
+
+ it('should render list of all groups', async () => {
+ renderGroupsApp();
+
+ expect(await ui.allFilter.find()).toBeInTheDocument();
+
+ expect(ui.localGroupRowWithLocalBadge.get()).toBeInTheDocument();
+ expect(ui.managedGroupRow.get()).toBeInTheDocument();
+ });
+
+ it('should render list of managed groups', async () => {
+ const user = userEvent.setup();
+ renderGroupsApp();
+
+ await user.click(await ui.managedFilter.find());
+
+ expect(ui.localGroupRow.query()).not.toBeInTheDocument();
+ expect(ui.managedGroupRow.get()).toBeInTheDocument();
+ });
+
+ it('should render list of local groups', async () => {
+ const user = userEvent.setup();
+ renderGroupsApp();
+
+ await user.click(await ui.localFilter.find());
+
+ expect(ui.localGroupRowWithLocalBadge.get()).toBeInTheDocument();
+ expect(ui.managedGroupRow.query()).not.toBeInTheDocument();
+ });
+});
+
+function renderGroupsApp() {
+ return renderApp('admin/groups', <App />);
+}
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx
deleted file mode 100644
index d0e25d51f7c..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import { click } from '../../../../helpers/testUtils';
-import Header from '../Header';
-
-it('should create new group', () => {
- const onCreate = jest.fn(() => Promise.resolve());
- const wrapper = shallow(<Header onCreate={onCreate} />);
- expect(wrapper).toMatchSnapshot();
-
- click(wrapper.find('[id="groups-create"]'));
- expect(wrapper).toMatchSnapshot();
-
- wrapper.find('Form').prop<Function>('onSubmit')({ name: 'foo', description: 'bar' });
- expect(onCreate).toHaveBeenCalledWith({ name: 'foo', description: 'bar' });
-});
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/List-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/List-test.tsx
index 625a816334a..a003332f77b 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/List-test.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/List-test.tsx
@@ -19,6 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockGroup } from '../../../../helpers/testMocks';
import List from '../List';
it('should render', () => {
@@ -31,9 +32,9 @@ it('should not render "Anyone"', () => {
function shallowRender(showAnyone = true) {
const groups = [
- { id: 1, name: 'sonar-users', description: '', membersCount: 55, default: true },
- { id: 2, name: 'foo', description: 'foobar', membersCount: 0, default: false },
- { id: 3, name: 'bar', description: 'barbar', membersCount: 1, default: false },
+ mockGroup({ name: 'sonar-users', description: '', membersCount: 55, default: true }),
+ mockGroup({ name: 'foo', description: 'foobar', membersCount: 0, default: false }),
+ mockGroup({ name: 'bar', description: 'barbar', membersCount: 1, default: false }),
];
return shallow(
<List
@@ -42,6 +43,7 @@ function shallowRender(showAnyone = true) {
onEdit={jest.fn()}
onEditMembers={jest.fn()}
showAnyone={showAnyone}
+ manageProvider={undefined}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/ListItem-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/ListItem-test.tsx
index 7bcfafa6968..206257c15cd 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/ListItem-test.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/ListItem-test.tsx
@@ -34,6 +34,7 @@ function shallowRender(overrides: Partial<ListItemProps> = {}) {
onDelete={jest.fn()}
onEdit={jest.fn()}
onEditMembers={jest.fn()}
+ manageProvider={undefined}
{...overrides}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap
deleted file mode 100644
index a06d0810832..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/App-test.tsx.snap
+++ /dev/null
@@ -1,94 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Fragment>
- <Suggestions
- suggestions="user_groups"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- prioritizeSeoTags={false}
- title="user_groups.page"
- />
- <main
- className="page page-limited"
- id="groups-page"
- >
- <Header
- onCreate={[Function]}
- />
- <SearchBox
- className="big-spacer-bottom"
- id="groups-search"
- minLength={2}
- onChange={[Function]}
- placeholder="search.search_by_name"
- value=""
- />
- </main>
-</Fragment>
-`;
-
-exports[`should render correctly 2`] = `
-<Fragment>
- <Suggestions
- suggestions="user_groups"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- prioritizeSeoTags={false}
- title="user_groups.page"
- />
- <main
- className="page page-limited"
- id="groups-page"
- >
- <Header
- onCreate={[Function]}
- />
- <SearchBox
- className="big-spacer-bottom"
- id="groups-search"
- minLength={2}
- onChange={[Function]}
- placeholder="search.search_by_name"
- value=""
- />
- <List
- groups={
- [
- {
- "default": false,
- "description": "Owners of organization foo",
- "membersCount": 1,
- "name": "Owners",
- },
- {
- "default": true,
- "description": "Members of organization foo",
- "membersCount": 2,
- "name": "Members",
- },
- ]
- }
- onDelete={[Function]}
- onEdit={[Function]}
- onEditMembers={[Function]}
- showAnyone={true}
- />
- <div
- id="groups-list-footer"
- >
- <ListFooter
- count={3}
- loadMore={[Function]}
- loading={false}
- ready={true}
- total={5}
- />
- </div>
- </main>
-</Fragment>
-`;
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/DeleteForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/DeleteForm-test.tsx.snap
deleted file mode 100644
index 6f7b6125eff..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/DeleteForm-test.tsx.snap
+++ /dev/null
@@ -1,45 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render 1`] = `
-<Modal
- contentLabel="groups.delete_group"
- onRequestClose={[MockFunction]}
->
- <form
- onSubmit={[Function]}
- >
- <header
- className="modal-head"
- >
- <h2>
- groups.delete_group
- </h2>
- </header>
- <div
- className="modal-body"
- >
- groups.delete_group.confirmation.Foo
- </div>
- <footer
- className="modal-foot"
- >
- <DeferredSpinner
- className="spacer-right"
- loading={false}
- />
- <SubmitButton
- className="button-red"
- disabled={false}
- >
- delete
- </SubmitButton>
- <ResetButtonLink
- disabled={false}
- onClick={[Function]}
- >
- cancel
- </ResetButtonLink>
- </footer>
- </form>
-</Modal>
-`;
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 b074ac3da9b..044ea201e66 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
@@ -3,10 +3,10 @@
exports[`should edit members 1`] = `
<Fragment>
<ButtonIcon
- aria-label="groups.users.edit"
+ aria-label="groups.users.edit.Foo"
className="button-small"
onClick={[Function]}
- title="groups.users.edit"
+ title="groups.users.edit.Foo"
>
<BulletListIcon />
</ButtonIcon>
@@ -16,17 +16,17 @@ exports[`should edit members 1`] = `
exports[`should edit members 2`] = `
<Fragment>
<ButtonIcon
- aria-label="groups.users.edit"
+ aria-label="groups.users.edit.Foo"
className="button-small"
onClick={[Function]}
- title="groups.users.edit"
+ title="groups.users.edit.Foo"
>
<BulletListIcon />
</ButtonIcon>
<EditMembersModal
group={
{
- "id": 3,
+ "managed": false,
"membersCount": 5,
"name": "Foo",
}
@@ -39,10 +39,10 @@ exports[`should edit members 2`] = `
exports[`should edit members 3`] = `
<Fragment>
<ButtonIcon
- aria-label="groups.users.edit"
+ aria-label="groups.users.edit.Foo"
className="button-small"
onClick={[Function]}
- title="groups.users.edit"
+ title="groups.users.edit.Foo"
>
<BulletListIcon />
</ButtonIcon>
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap
deleted file mode 100644
index 1f5ac9699d7..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap
+++ /dev/null
@@ -1,82 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render form 1`] = `
-<Modal
- contentLabel="header"
- onRequestClose={[MockFunction]}
- size="small"
->
- <form
- onSubmit={[Function]}
- >
- <header
- className="modal-head"
- >
- <h2>
- header
- </h2>
- </header>
- <div
- className="modal-body"
- >
- <MandatoryFieldsExplanation
- className="modal-field"
- />
- <div
- className="modal-field"
- >
- <label
- htmlFor="create-group-name"
- >
- name
- <MandatoryFieldMarker />
- </label>
- <input
- autoFocus={true}
- id="create-group-name"
- maxLength={255}
- name="name"
- onChange={[Function]}
- required={true}
- size={50}
- type="text"
- value=""
- />
- </div>
- <div
- className="modal-field"
- >
- <label
- htmlFor="create-group-description"
- >
- description
- </label>
- <textarea
- id="create-group-description"
- name="description"
- onChange={[Function]}
- value=""
- />
- </div>
- </div>
- <footer
- className="modal-foot"
- >
- <DeferredSpinner
- className="spacer-right"
- loading={false}
- />
- <SubmitButton
- disabled={false}
- >
- confirmButtonText
- </SubmitButton>
- <ResetButtonLink
- onClick={[Function]}
- >
- cancel
- </ResetButtonLink>
- </footer>
- </form>
-</Modal>
-`;
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap
deleted file mode 100644
index ba792ca89fc..00000000000
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap
+++ /dev/null
@@ -1,69 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should create new group 1`] = `
-<Fragment>
- <div
- className="page-header"
- id="groups-header"
- >
- <h2
- className="page-title"
- >
- user_groups.page
- </h2>
- <div
- className="page-actions"
- >
- <Button
- disabled={false}
- id="groups-create"
- onClick={[Function]}
- >
- groups.create_group
- </Button>
- </div>
- <p
- className="page-description"
- >
- user_groups.page.description
- </p>
- </div>
-</Fragment>
-`;
-
-exports[`should create new group 2`] = `
-<Fragment>
- <div
- className="page-header"
- id="groups-header"
- >
- <h2
- className="page-title"
- >
- user_groups.page
- </h2>
- <div
- className="page-actions"
- >
- <Button
- disabled={false}
- id="groups-create"
- onClick={[Function]}
- >
- groups.create_group
- </Button>
- </div>
- <p
- className="page-description"
- >
- user_groups.page.description
- </p>
- </div>
- <Form
- confirmButtonText="create"
- header="groups.create_group"
- onClose={[Function]}
- onSubmit={[MockFunction]}
- />
-</Fragment>
-`;
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap
index 5d3a39ef7ca..19ddc35298e 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap
@@ -64,7 +64,7 @@ exports[`should render 1`] = `
{
"default": false,
"description": "barbar",
- "id": 3,
+ "managed": false,
"membersCount": 1,
"name": "bar",
}
@@ -79,7 +79,7 @@ exports[`should render 1`] = `
{
"default": false,
"description": "foobar",
- "id": 2,
+ "managed": false,
"membersCount": 0,
"name": "foo",
}
@@ -94,7 +94,7 @@ exports[`should render 1`] = `
{
"default": true,
"description": "",
- "id": 1,
+ "managed": false,
"membersCount": 55,
"name": "sonar-users",
}
diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/ListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/ListItem-test.tsx.snap
index 6e22bae2cf0..ca99c67a45f 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/ListItem-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/ListItem-test.tsx.snap
@@ -24,6 +24,7 @@ exports[`should render correctly 1`] = `
<EditMembers
group={
{
+ "managed": false,
"membersCount": 1,
"name": "Foo",
}
@@ -41,7 +42,9 @@ exports[`should render correctly 1`] = `
<td
className="thin nowrap text-right"
>
- <ActionsDropdown>
+ <ActionsDropdown
+ label="groups.edit.Foo"
+ >
<ActionsDropdownItem
className="js-group-update"
onClick={[Function]}