mockGroup,
mockIdentityProvider,
mockPaging,
- mockUser,
+ mockUserGroupMember,
} from '../../helpers/testMocks';
import {
Group,
UserGroupMember,
} from '../../types/types';
import { getSystemInfo } from '../system';
-import { getIdentityProviders } from '../users';
import {
+ addUserToGroup,
createGroup,
deleteGroup,
getUsersInGroup,
+ removeUserFromGroup,
searchUsersGroups,
updateGroup,
} from '../user_groups';
+import { getIdentityProviders } from '../users';
+
+jest.mock('../users');
+jest.mock('../system');
+jest.mock('../user_groups');
export default class GroupsServiceMock {
isManaged = false;
paging: Paging;
groups: Group[];
+ users: UserGroupMember[];
readOnlyGroups = [
mockGroup({ name: 'managed-group', managed: true }),
mockGroup({ name: 'local-group', managed: false }),
];
+ defaultUsers = [
+ mockUserGroupMember({ name: 'alice', login: 'alice.dev' }),
+ mockUserGroupMember({ name: 'bob', login: 'bob.dev' }),
+ mockUserGroupMember({ selected: false }),
+ ];
+
constructor() {
this.groups = cloneDeep(this.readOnlyGroups);
this.paging = mockPaging({
pageSize: 2,
total: 200,
});
+ this.users = cloneDeep(this.defaultUsers);
jest.mocked(getSystemInfo).mockImplementation(this.handleGetSystemInfo);
jest.mocked(getIdentityProviders).mockImplementation(this.handleGetIdentityProviders);
jest.mocked(deleteGroup).mockImplementation((g) => this.handleDeleteGroup(g));
jest.mocked(updateGroup).mockImplementation((g) => this.handleUpdateGroup(g));
jest.mocked(getUsersInGroup).mockImplementation(this.handlegetUsersInGroup);
+ jest.mocked(addUserToGroup).mockImplementation(this.handleAddUserToGroup);
+ jest.mocked(removeUserFromGroup).mockImplementation(this.handleRemoveUserFromGroup);
}
reset() {
this.groups = cloneDeep(this.readOnlyGroups);
+ this.users = cloneDeep(this.defaultUsers);
}
setIsManaged(managed: boolean) {
}): Promise<Paging & { users: UserGroupMember[] }> => {
return this.reply({
...this.paging,
- users: [
- {
- ...mockUser({ name: 'alice' }),
- selected: true,
- } as UserGroupMember,
- {
- ...mockUser({ name: 'bob' }),
- selected: false,
- } as UserGroupMember,
- ].filter((u) => u.name.includes(data.q ?? '')),
+ users: this.users
+ .filter((u) => u.name.includes(data.q ?? ''))
+ .filter((u) => {
+ switch (data.selected) {
+ case 'selected':
+ return u.selected;
+ case 'deselected':
+ return !u.selected;
+ default:
+ return true;
+ }
+ }),
});
};
);
};
+ handleAddUserToGroup: typeof addUserToGroup = ({ login }) => {
+ this.users = this.users.map((u) => (u.login === login ? { ...u, selected: true } : u));
+ return this.reply({});
+ };
+
+ handleRemoveUserFromGroup: typeof removeUserFromGroup = ({ login }) => {
+ this.users = this.users.map((u) => (u.login === login ? { ...u, selected: false } : u));
+ return this.reply({});
+ };
+
reply<T>(response: T): Promise<T> {
return Promise.resolve(cloneDeep(response));
}
return Promise.reject('x_x');
}
+ if (this.tokens.some((t) => t.name === name)) {
+ return Promise.reject('This name is already used');
+ }
+
const token = {
name,
login,
};
handleRevokeToken = ({ name }: { name: string; login?: string }) => {
- const index = this.tokens.findIndex((t) => t.name === name);
-
- if (index < 0) {
- return Promise.resolve();
- }
-
- this.tokens.splice(index, 1);
+ this.tokens = this.tokens.filter((t) => t.name !== name);
return Promise.resolve();
};
*/
import { isAfter, isBefore } from 'date-fns';
-import { cloneDeep } from 'lodash';
+import { cloneDeep, isEmpty, isUndefined, omitBy } from 'lodash';
import { mockClusterSysInfo, mockIdentityProvider, mockUser } from '../../helpers/testMocks';
import { IdentityProvider, Paging, SysInfoCluster } from '../../types/types';
-import { User } from '../../types/users';
+import { ChangePasswordResults, User } from '../../types/users';
import { getSystemInfo } from '../system';
-import { createUser, getIdentityProviders, searchUsers } from '../users';
+import { addUserToGroup, removeUserFromGroup } from '../user_groups';
+import {
+ UserGroup,
+ changePassword,
+ createUser,
+ deactivateUser,
+ getIdentityProviders,
+ getUserGroups,
+ searchUsers,
+ updateUser,
+} from '../users';
+
+jest.mock('../users');
+jest.mock('../user_groups');
+jest.mock('../system');
const DEFAULT_USERS = [
mockUser({
name: 'Alice Merveille',
lastConnectionDate: '2023-06-27T17:08:59+0200',
sonarLintLastConnectionDate: '2023-05-27T17:08:59+0200',
+ groups: ['group1', 'group2', 'group3', 'group4'],
}),
mockUser({
managed: false,
+ local: false,
login: 'charlie.cox',
name: 'Charlie Cox',
lastConnectionDate: '2023-06-25T17:08:59+0200',
sonarLintLastConnectionDate: '2023-06-20T12:10:59+0200',
+ externalProvider: 'test',
+ externalIdentity: 'ExternalTest',
}),
mockUser({
managed: true,
+ local: false,
+ externalProvider: 'test2',
+ externalIdentity: 'UnknownExternalProvider',
login: 'denis.villeneuve',
name: 'Denis Villeneuve',
lastConnectionDate: '2023-06-20T15:08:59+0200',
}),
];
+const DEFAULT_GROUPS: UserGroup[] = [
+ {
+ id: 1001,
+ name: 'test1',
+ description: 'test1',
+ selected: true,
+ default: true,
+ },
+ {
+ id: 1002,
+ name: 'test2',
+ description: 'test2',
+ selected: true,
+ default: false,
+ },
+ {
+ id: 1003,
+ name: 'test3',
+ description: 'test3',
+ selected: false,
+ default: false,
+ },
+];
+
+const DEFAULT_PASSWORD = 'test';
+
export default class UsersServiceMock {
isManaged = true;
users = cloneDeep(DEFAULT_USERS);
+ groups = cloneDeep(DEFAULT_GROUPS);
+ password = DEFAULT_PASSWORD;
constructor() {
jest.mocked(getSystemInfo).mockImplementation(this.handleGetSystemInfo);
jest.mocked(getIdentityProviders).mockImplementation(this.handleGetIdentityProviders);
jest.mocked(searchUsers).mockImplementation((p) => this.handleSearchUsers(p));
jest.mocked(createUser).mockImplementation(this.handleCreateUser);
+ jest.mocked(updateUser).mockImplementation(this.handleUpdateUser);
+ jest.mocked(getUserGroups).mockImplementation(this.handleGetUserGroups);
+ jest.mocked(addUserToGroup).mockImplementation(this.handleAddUserToGroup);
+ jest.mocked(removeUserFromGroup).mockImplementation(this.handleRemoveUserFromGroup);
+ jest.mocked(changePassword).mockImplementation(this.handleChangePassword);
+ jest.mocked(deactivateUser).mockImplementation(this.handleDeactivateUser);
}
setIsManaged(managed: boolean) {
scmAccount: string[];
}) => {
const { email, local, login, name, scmAccount } = data;
+ if (scmAccount.some((a) => isEmpty(a.trim()))) {
+ return Promise.reject({
+ status: 400,
+ json: () => Promise.resolve({ errors: [{ msg: 'Error: Empty SCM' }] }),
+ });
+ }
const newUser = mockUser({
email,
local,
return this.reply(undefined);
};
+ handleUpdateUser = (data: {
+ email?: string;
+ login: string;
+ name: string;
+ scmAccount: string[];
+ }) => {
+ const { email, login, name, scmAccount } = data;
+ const user = this.users.find((u) => u.login === login);
+ if (!user) {
+ return Promise.reject('No such user');
+ }
+ Object.assign(user, {
+ ...omitBy({ name, email, scmAccount }, isUndefined),
+ });
+ return this.reply({ user });
+ };
+
handleGetIdentityProviders = (): Promise<{ identityProviders: IdentityProvider[] }> => {
- return this.reply({ identityProviders: [mockIdentityProvider()] });
+ return this.reply({
+ identityProviders: [mockIdentityProvider({ key: 'test' })],
+ });
};
handleGetSystemInfo = (): Promise<SysInfoCluster> => {
);
};
+ handleGetUserGroups: typeof getUserGroups = (data) => {
+ const filteredGroups = this.groups
+ .filter((g) => g.name.includes(data.q ?? ''))
+ .filter((g) => {
+ switch (data.selected) {
+ case 'selected':
+ return g.selected;
+ case 'deselected':
+ return !g.selected;
+ default:
+ return true;
+ }
+ });
+ return this.reply({
+ paging: { pageIndex: 1, pageSize: 10, total: filteredGroups.length },
+ groups: filteredGroups,
+ });
+ };
+
+ handleAddUserToGroup: typeof addUserToGroup = ({ name }) => {
+ this.groups = this.groups.map((g) => (g.name === name ? { ...g, selected: true } : g));
+ return this.reply({});
+ };
+
+ handleRemoveUserFromGroup: typeof removeUserFromGroup = ({ name }) => {
+ let isDefault = false;
+ this.groups = this.groups.map((g) => {
+ if (g.name === name) {
+ if (g.default) {
+ isDefault = true;
+ return g;
+ }
+ return { ...g, selected: false };
+ }
+ return g;
+ });
+ return isDefault
+ ? Promise.reject({
+ errors: [{ msg: 'Cannot remove Default group' }],
+ })
+ : this.reply({});
+ };
+
+ handleChangePassword: typeof changePassword = (data) => {
+ if (data.previousPassword !== this.password) {
+ return Promise.reject(ChangePasswordResults.OldPasswordIncorrect);
+ }
+ if (data.password === this.password) {
+ return Promise.reject(ChangePasswordResults.NewPasswordSameAsOld);
+ }
+ this.password = data.password;
+ return this.reply({});
+ };
+
+ handleDeactivateUser: typeof deactivateUser = (data) => {
+ const index = this.users.findIndex((u) => u.login === data.login);
+ const user = this.users.splice(index, 1)[0];
+ user.active = false;
+ return this.reply({ user });
+ };
+
reset = () => {
this.isManaged = true;
this.users = cloneDeep(DEFAULT_USERS);
+ this.groups = cloneDeep(DEFAULT_GROUPS);
+ this.password = DEFAULT_PASSWORD;
};
reply<T>(response: T): Promise<T> {
login: string;
name?: string;
scmAccount: string[];
-}): Promise<User> {
+}): Promise<{ user: User }> {
return postJSON('/api/users/update', {
...data,
scmAccount: data.scmAccount.length > 0 ? data.scmAccount : '',
});
}
-export function deactivateUser(data: { login: string; anonymize?: boolean }): Promise<User> {
+export function deactivateUser(data: {
+ login: string;
+ anonymize?: boolean;
+}): Promise<{ user: User }> {
return postJSON('/api/users/deactivate', data).catch(throwGlobalError);
}
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import { useCallback, useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet-async';
+import { searchUsersGroups } from '../../api/user_groups';
+import ListFooter from '../../components/controls/ListFooter';
+import { ManagedFilter } from '../../components/controls/ManagedFilter';
+import SearchBox from '../../components/controls/SearchBox';
+import Suggestions from '../../components/embed-docs-modal/Suggestions';
+import { useManageProvider } from '../../components/hooks/useManageProvider';
+import { translate } from '../../helpers/l10n';
+import { Group, Paging } from '../../types/types';
+import Header from './components/Header';
+import List from './components/List';
+import './groups.css';
+
+export default function App() {
+ const [loading, setLoading] = useState<boolean>(true);
+ const [paging, setPaging] = useState<Paging>();
+ const [search, setSearch] = useState<string>('');
+ const [groups, setGroups] = useState<Group[]>([]);
+ const [managed, setManaged] = useState<boolean | undefined>();
+ const manageProvider = useManageProvider();
+
+ const fetchGroups = useCallback(async () => {
+ setLoading(true);
+ try {
+ const { groups, paging } = await searchUsersGroups({
+ q: search,
+ managed,
+ });
+ setGroups(groups);
+ setPaging(paging);
+ } finally {
+ setLoading(false);
+ }
+ }, [search, managed]);
+
+ const fetchMoreGroups = useCallback(async () => {
+ if (!paging) {
+ return;
+ }
+ setLoading(true);
+ try {
+ const { groups: nextGroups, paging: nextPage } = await searchUsersGroups({
+ q: search,
+ managed,
+ p: paging.pageIndex + 1,
+ });
+ setPaging(nextPage);
+ setGroups([...groups, ...nextGroups]);
+ } finally {
+ setLoading(false);
+ }
+ }, [groups, search, managed, paging]);
+
+ useEffect(() => {
+ fetchGroups();
+ }, [search, managed]);
+
+ return (
+ <>
+ <Suggestions suggestions="user_groups" />
+ <Helmet defer={false} title={translate('user_groups.page')} />
+ <main className="page page-limited" id="groups-page">
+ <Header reload={fetchGroups} manageProvider={manageProvider} />
+
+ <div className="display-flex-justify-start big-spacer-bottom big-spacer-top">
+ <ManagedFilter
+ manageProvider={manageProvider}
+ loading={loading}
+ managed={managed}
+ setManaged={setManaged}
+ />
+ <SearchBox
+ id="groups-search"
+ minLength={2}
+ onChange={(q) => setSearch(q)}
+ placeholder={translate('search.search_by_name')}
+ value={search}
+ />
+ </div>
+
+ <List groups={groups} reload={fetchGroups} manageProvider={manageProvider} />
+
+ {paging !== undefined && (
+ <div id="groups-list-footer">
+ <ListFooter
+ count={groups.length}
+ loading={loading}
+ loadMore={fetchMoreGroups}
+ ready={!loading}
+ total={paging.total}
+ />
+ </div>
+ )}
+ </main>
+ </>
+ );
+}
--- /dev/null
+/*
+ * 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, within } 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 '../GroupsApp';
+
+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' }),
+ selectedFilter: byRole('button', { name: 'selected' }),
+ unselectedFilter: byRole('button', { name: 'unselected' }),
+ 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' }),
+ reloadButton: byRole('button', { name: 'reload' }),
+ doneButton: byRole('button', { name: 'done' }),
+
+ createGroupDialog: byRole('dialog', { name: 'groups.create_group' }),
+ membersViewDialog: byRole('dialog', { name: 'users.list' }),
+ membersDialog: byRole('dialog', { name: 'users.update' }),
+ getMembers: () => within(ui.membersDialog.get()).getAllByRole('checkbox'),
+
+ managedGroupRow: byRole('row', { name: 'managed-group 1' }),
+ managedGroupEditMembersButton: byRole('button', { name: 'groups.users.edit.managed-group' }),
+ managedGroupViewMembersButton: byRole('button', { name: 'groups.users.view.managed-group' }),
+
+ memberAliceUser: byText('alice'),
+ memberBobUser: byText('bob'),
+ memberSearchInput: byRole('searchbox', { name: 'search_verb' }),
+
+ 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',
+ }),
+};
+
+beforeEach(() => {
+ handler.reset();
+});
+
+describe('in non managed mode', () => {
+ beforeEach(() => {
+ handler.setIsManaged(false);
+ });
+
+ it('should render all groups', async () => {
+ renderGroupsApp();
+
+ await act(async () => 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();
+
+ await act(async () => expect(await ui.description.find()).toBeInTheDocument());
+ await act(async () => {
+ await user.click(ui.createGroupButton.get());
+ });
+
+ expect(await ui.createGroupDialog.find()).toBeInTheDocument();
+
+ await act(async () => {
+ await user.type(ui.nameInput.get(), 'local-group 2');
+ await user.type(ui.descriptionInput.get(), 'group 2 is loco!');
+ 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 act(async () => {
+ 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 act(async () => {
+ await user.click(await ui.localEditButton.find());
+ await user.click(await ui.updateButton.find());
+ });
+
+ expect(ui.updateDialog.get()).toBeInTheDocument();
+
+ await act(async () => {
+ 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();
+
+ await act(async () => expect(await ui.localGroupRow.find()).toBeInTheDocument());
+ expect(await ui.localGroupEditMembersButton.find()).toBeInTheDocument();
+
+ await act(async () => {
+ await user.click(ui.localGroupEditMembersButton.get());
+ });
+
+ expect(await ui.membersDialog.find()).toBeInTheDocument();
+
+ expect(ui.getMembers()).toHaveLength(2);
+
+ await user.click(ui.allFilter.get());
+ expect(ui.getMembers()).toHaveLength(3);
+
+ await user.click(ui.unselectedFilter.get());
+ expect(ui.reloadButton.query()).not.toBeInTheDocument();
+ await user.click(ui.getMembers()[0]);
+ expect(await ui.reloadButton.find()).toBeInTheDocument();
+
+ await user.click(ui.selectedFilter.get());
+ expect(ui.getMembers()).toHaveLength(3);
+ expect(ui.reloadButton.query()).not.toBeInTheDocument();
+ await user.click(ui.getMembers()[0]);
+ expect(await ui.reloadButton.find()).toBeInTheDocument();
+ await user.click(ui.reloadButton.get());
+ expect(ui.getMembers()).toHaveLength(2);
+
+ await act(() => user.click(ui.doneButton.get()));
+ expect(ui.membersDialog.query()).not.toBeInTheDocument();
+ });
+
+ it('should be able search a group', async () => {
+ const user = userEvent.setup();
+ renderGroupsApp();
+
+ await act(async () => expect(await ui.localGroupRow.find()).toBeInTheDocument());
+ expect(ui.managedGroupRow.get()).toBeInTheDocument();
+
+ await act(async () => {
+ 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();
+
+ await act(async () => expect(await ui.localGroupRow.find()).toBeInTheDocument());
+ expect(await screen.findAllByRole('row')).toHaveLength(3);
+
+ await act(async () => {
+ await user.click(await ui.showMore.find());
+ });
+
+ expect(await screen.findAllByRole('row')).toHaveLength(5);
+ });
+});
+
+describe('in manage mode', () => {
+ beforeEach(() => {
+ handler.setIsManaged(true);
+ });
+
+ it('should not be able to create a group', async () => {
+ renderGroupsApp();
+ await act(async () => 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();
+
+ await act(async () => expect(await ui.localGroupRowWithLocalBadge.find()).toBeInTheDocument());
+
+ await act(async () => {
+ await user.click(await ui.localFilter.find());
+ await user.click(await ui.localEditButton.find());
+ });
+ expect(ui.updateButton.query()).not.toBeInTheDocument();
+
+ await act(async () => {
+ 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 () => {
+ const user = userEvent.setup();
+ renderGroupsApp();
+
+ await act(async () => expect(await ui.managedGroupRow.find()).toBeInTheDocument());
+ expect(ui.managedEditButton.query()).not.toBeInTheDocument();
+
+ expect(ui.managedGroupEditMembersButton.query()).not.toBeInTheDocument();
+
+ await act(() => user.click(ui.managedGroupViewMembersButton.get()));
+ expect(await ui.membersViewDialog.find()).toBeInTheDocument();
+
+ expect(ui.memberAliceUser.get()).toBeInTheDocument();
+ expect(ui.memberBobUser.get()).toBeInTheDocument();
+
+ await act(() => user.type(ui.memberSearchInput.get(), 'b'));
+
+ expect(await ui.memberBobUser.find()).toBeInTheDocument();
+ expect(ui.memberAliceUser.query()).not.toBeInTheDocument();
+ });
+
+ it('should render list of all groups', async () => {
+ renderGroupsApp();
+
+ await act(async () => 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 act(async () => {
+ 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 act(async () => {
+ 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 />);
+}
+++ /dev/null
-/*
- * 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 * as React from 'react';
-import { useCallback, useEffect, useState } from 'react';
-import { Helmet } from 'react-helmet-async';
-import { searchUsersGroups } from '../../../api/user_groups';
-import ListFooter from '../../../components/controls/ListFooter';
-import { ManagedFilter } from '../../../components/controls/ManagedFilter';
-import SearchBox from '../../../components/controls/SearchBox';
-import Suggestions from '../../../components/embed-docs-modal/Suggestions';
-import { useManageProvider } from '../../../components/hooks/useManageProvider';
-import { translate } from '../../../helpers/l10n';
-import { Group, Paging } from '../../../types/types';
-import '../groups.css';
-import Header from './Header';
-import List from './List';
-
-export default function App() {
- const [loading, setLoading] = useState<boolean>(true);
- const [paging, setPaging] = useState<Paging>();
- const [search, setSearch] = useState<string>('');
- const [groups, setGroups] = useState<Group[]>([]);
- const [managed, setManaged] = useState<boolean | undefined>();
- const manageProvider = useManageProvider();
-
- const fetchGroups = useCallback(async () => {
- setLoading(true);
- try {
- const { groups, paging } = await searchUsersGroups({
- q: search,
- managed,
- });
- setGroups(groups);
- setPaging(paging);
- } finally {
- setLoading(false);
- }
- }, [search, managed]);
-
- const fetchMoreGroups = useCallback(async () => {
- if (!paging) {
- return;
- }
- setLoading(true);
- try {
- const { groups: nextGroups, paging: nextPage } = await searchUsersGroups({
- q: search,
- managed,
- p: paging.pageIndex + 1,
- });
- setPaging(nextPage);
- setGroups([...groups, ...nextGroups]);
- } finally {
- setLoading(false);
- }
- }, [groups, search, managed, paging]);
-
- useEffect(() => {
- fetchGroups();
- }, [search, managed]);
-
- return (
- <>
- <Suggestions suggestions="user_groups" />
- <Helmet defer={false} title={translate('user_groups.page')} />
- <main className="page page-limited" id="groups-page">
- <Header reload={fetchGroups} manageProvider={manageProvider} />
-
- <div className="display-flex-justify-start big-spacer-bottom big-spacer-top">
- <ManagedFilter
- manageProvider={manageProvider}
- loading={loading}
- managed={managed}
- setManaged={setManaged}
- />
- <SearchBox
- id="groups-search"
- minLength={2}
- onChange={(q) => setSearch(q)}
- placeholder={translate('search.search_by_name')}
- value={search}
- />
- </div>
-
- <List groups={groups} reload={fetchGroups} manageProvider={manageProvider} />
-
- {paging !== undefined && (
- <div id="groups-list-footer">
- <ListFooter
- count={groups.length}
- loading={loading}
- loadMore={fetchMoreGroups}
- ready={!loading}
- total={paging.total}
- />
- </div>
- )}
- </main>
- </>
- );
-}
+++ /dev/null
-/*
- * 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 { 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 = mockGroup({ name: 'foo', membersCount: 1 });
-
-jest.mock('../../../../api/user_groups', () => ({
- getUsersInGroup: jest.fn().mockResolvedValue({
- paging: { pageIndex: 1, pageSize: 10, total: 1 },
- users: [
- {
- login: 'foo',
- name: 'bar',
- selected: true,
- },
- ],
- }),
- addUserToGroup: jest.fn().mockResolvedValue({}),
- removeUserFromGroup: jest.fn().mockResolvedValue({}),
-}));
-
-beforeEach(() => {
- jest.clearAllMocks();
-});
-
-it('should render modal properly', async () => {
- const wrapper = shallowRender();
- wrapper.find(SelectList).props().onSearch({
- query: '',
- filter: SelectListFilter.Selected,
- page: 1,
- pageSize: 100,
- });
- await waitAndUpdate(wrapper);
- expect(wrapper.state().needToReload).toBe(false);
-
- expect(wrapper.instance().mounted).toBe(true);
- expect(wrapper).toMatchSnapshot();
- expect(wrapper.instance().renderElement('test1')).toMatchSnapshot();
- expect(wrapper.instance().renderElement('test_foo')).toMatchSnapshot();
-
- expect(getUsersInGroup).toHaveBeenCalledWith(
- expect.objectContaining({
- name: group.name,
- p: 1,
- ps: 100,
- q: undefined,
- selected: SelectListFilter.Selected,
- })
- );
-
- wrapper.instance().componentWillUnmount();
- expect(wrapper.instance().mounted).toBe(false);
-});
-
-it('should handle selection properly', async () => {
- const wrapper = shallowRender();
- wrapper.instance().handleSelect('toto');
- await waitAndUpdate(wrapper);
-
- expect(addUserToGroup).toHaveBeenCalledWith(
- expect.objectContaining({
- name: group.name,
- login: 'toto',
- })
- );
- expect(wrapper.state().needToReload).toBe(true);
-});
-
-it('should handle deselection properly', async () => {
- const wrapper = shallowRender();
- wrapper.instance().handleUnselect('tata');
-
- await waitAndUpdate(wrapper);
- expect(removeUserFromGroup).toHaveBeenCalledWith(
- expect.objectContaining({
- name: group.name,
- login: 'tata',
- })
- );
- expect(wrapper.state().needToReload).toBe(true);
-});
-
-function shallowRender(props: Partial<EditMembersModal['props']> = {}) {
- return shallow<EditMembersModal>(
- <EditMembersModal group={group} onClose={jest.fn()} {...props} />
- );
-}
+++ /dev/null
-/*
- * 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 '../GroupsApp';
-
-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' }),
- membersViewDialog: byRole('dialog', { name: 'users.list' }),
- membersDialog: byRole('dialog', { name: 'users.update' }),
-
- managedGroupRow: byRole('row', { name: 'managed-group 1' }),
- managedGroupEditMembersButton: byRole('button', { name: 'groups.users.edit.managed-group' }),
- managedGroupViewMembersButton: byRole('button', { name: 'groups.users.view.managed-group' }),
-
- memberAliceUser: byText('alice'),
- memberBobUser: byText('bob'),
- memberSearchInput: byRole('searchbox', { name: 'search_verb' }),
-
- 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 act(async () => {
- await user.click(ui.createGroupButton.get());
- });
-
- expect(await ui.createGroupDialog.find()).toBeInTheDocument();
-
- await act(async () => {
- await user.type(ui.nameInput.get(), 'local-group 2');
- await user.type(ui.descriptionInput.get(), 'group 2 is loco!');
- 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 act(async () => {
- 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 act(async () => {
- await user.click(await ui.localEditButton.find());
- await user.click(await ui.updateButton.find());
- });
-
- expect(ui.updateDialog.get()).toBeInTheDocument();
-
- await act(async () => {
- 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 act(async () => {
- 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 act(async () => {
- 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();
-
- expect(await ui.localGroupRow.find()).toBeInTheDocument();
- expect(await screen.findAllByRole('row')).toHaveLength(3);
-
- await act(async () => {
- await user.click(await ui.showMore.find());
- });
-
- expect(await screen.findAllByRole('row')).toHaveLength(5);
- });
-});
-
-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 act(async () => {
- await user.click(await ui.localFilter.find());
- await user.click(await ui.localEditButton.find());
- });
- expect(ui.updateButton.query()).not.toBeInTheDocument();
-
- await act(async () => {
- 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();
-
- await userEvent.click(ui.managedGroupViewMembersButton.get());
- expect(await ui.membersViewDialog.find()).toBeInTheDocument();
-
- expect(ui.memberAliceUser.get()).toBeInTheDocument();
- expect(ui.memberBobUser.get()).toBeInTheDocument();
-
- await userEvent.type(ui.memberSearchInput.get(), 'b');
-
- expect(await ui.memberBobUser.find()).toBeInTheDocument();
- expect(ui.memberAliceUser.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 act(async () => {
- 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 act(async () => {
- 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 />);
-}
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render modal properly 1`] = `
-<Modal
- className="group-menbers-modal"
- contentLabel="users.update"
- onRequestClose={[MockFunction]}
->
- <header
- className="modal-head"
- >
- <h2>
- users.update
- </h2>
- </header>
- <div
- className="modal-body modal-container"
- >
- <SelectList
- elements={
- [
- "foo",
- ]
- }
- needToReload={false}
- onSearch={[Function]}
- onSelect={[Function]}
- onUnselect={[Function]}
- renderElement={[Function]}
- selectedElements={
- [
- "foo",
- ]
- }
- withPaging={true}
- />
- </div>
- <footer
- className="modal-foot"
- >
- <ResetButtonLink
- onClick={[MockFunction]}
- >
- done
- </ResetButtonLink>
- </footer>
-</Modal>
-`;
-
-exports[`should render modal properly 2`] = `
-<div
- className="select-list-list-item"
->
- test1
-</div>
-`;
-
-exports[`should render modal properly 3`] = `
-<div
- className="select-list-list-item"
->
- test_foo
-</div>
-`;
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import App from './components/GroupsApp';
+import App from './GroupsApp';
const routes = () => <Route path="groups" element={<App />} />;
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { act } from '@testing-library/react';
+import { act, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
import selectEvent from 'react-select-event';
import { byLabelText, byRole, byText } from 'testing-library-selector';
+import ComponentsServiceMock from '../../../api/mocks/ComponentsServiceMock';
+import SettingsServiceMock from '../../../api/mocks/SettingsServiceMock';
+import UserTokensMock from '../../../api/mocks/UserTokensMock';
import UsersServiceMock from '../../../api/mocks/UsersServiceMock';
+import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
import { renderApp } from '../../../helpers/testReactTestingUtils';
+import { ChangePasswordResults, CurrentUser } from '../../../types/users';
import UsersApp from '../UsersApp';
-jest.mock('../../../api/users');
-jest.mock('../../../api/system');
+jest.mock('../../../api/user-tokens');
+jest.mock('../../../api/components');
+jest.mock('../../../api/settings');
-const handler = new UsersServiceMock();
+const userHandler = new UsersServiceMock();
+const tokenHandler = new UserTokensMock();
+const componentsHandler = new ComponentsServiceMock();
+const settingsHandler = new SettingsServiceMock();
const ui = {
createUserButton: byRole('button', { name: 'users.create_user' }),
- infoManageMode: byText(/users\.page\.managed_description/),
- description: byText('users.page.description'),
allFilter: byRole('button', { name: 'all' }),
+ selectedFilter: byRole('button', { name: 'selected' }),
+ unselectedFilter: byRole('button', { name: 'unselected' }),
managedFilter: byRole('button', { name: 'managed' }),
localFilter: byRole('button', { name: 'local' }),
- searchInput: byRole('searchbox', { name: 'search.search_by_login_or_name' }),
- activityFilter: byRole('combobox', { name: 'users.activity_filter.label' }),
+ showMore: byRole('button', { name: 'show_more' }),
+ aliceUpdateGroupButton: byRole('button', { name: 'users.update_users_groups.alice.merveille' }),
+ aliceUpdateButton: byRole('button', { name: 'users.manage_user.alice.merveille' }),
+ alicedDeactivateButton: byRole('button', { name: 'users.deactivate' }),
+ bobUpdateGroupButton: byRole('button', { name: 'users.update_users_groups.bob.marley' }),
+ bobUpdateButton: byRole('button', { name: 'users.manage_user.bob.marley' }),
+ scmAddButton: byRole('button', { name: 'add_verb' }),
+ createUserDialogButton: byRole('button', { name: 'create' }),
+ reloadButton: byRole('button', { name: 'reload' }),
+ doneButton: byRole('button', { name: 'done' }),
+ changeButton: byRole('button', { name: 'change_verb' }),
+ revokeButton: byRole('button', { name: 'users.tokens.revoke' }),
+ generateButton: byRole('button', { name: 'users.generate' }),
+ sureButton: byRole('button', { name: 'users.tokens.sure' }),
+ updateButton: byRole('button', { name: 'update_verb' }),
+ deleteSCMButton: (value?: string) =>
+ byRole('button', {
+ name: `remove_x.users.create_user.scm_account_${value ? `x.${value}` : 'new'}`,
+ }),
+
userRows: byRole('row', {
name: (accessibleName) => /^[A-Z]+ /.test(accessibleName),
}),
- showMore: byRole('button', { name: 'show_more' }),
aliceRow: byRole('row', {
name: (accessibleName) => accessibleName.startsWith('AM Alice Merveille alice.merveille '),
}),
name: (accessibleName) =>
accessibleName.startsWith('AM Alice Merveille alice.merveille local '),
}),
- aliceUpdateGroupButton: byRole('button', { name: 'users.update_users_groups.alice.merveille' }),
- aliceUpdateButton: byRole('button', { name: 'users.manage_user.alice.merveille' }),
- alicedDeactivateButton: byRole('button', { name: 'users.deactivate' }),
- bobUpdateGroupButton: byRole('button', { name: 'users.update_users_groups.bob.marley' }),
- bobUpdateButton: byRole('button', { name: 'users.manage_user.bob.marley' }),
bobRow: byRole('row', {
name: (accessibleName) => accessibleName.startsWith('BM Bob Marley bob.marley '),
}),
charlieRow: byRole('row', {
- name: (accessibleName) => accessibleName.startsWith('CC Charlie Cox charlie.cox local '),
+ name: (accessibleName) => accessibleName.startsWith('CC Charlie Cox charlie.cox'),
}),
denisRow: byRole('row', {
name: (accessibleName) => accessibleName.startsWith('DV Denis Villeneuve denis.villeneuve '),
name: (accessibleName) => accessibleName.startsWith('EG Eva Green eva.green '),
}),
franckRow: byRole('row', {
- name: (accessibleName) => accessibleName.startsWith('FG Franck Grillo franck.grillo local '),
+ name: (accessibleName) => accessibleName.startsWith('FG Franck Grillo franck.grillo '),
}),
+ jackRow: byRole('row', { name: /Jack/ }),
+
+ dialogGroups: byRole('dialog', { name: 'users.update_groups' }),
+ getGroups: () => within(ui.dialogGroups.get()).getAllByRole('checkbox'),
+ dialogTokens: byRole('dialog', { name: 'users.tokens' }),
+ dialogPasswords: byRole('dialog', { name: 'my_profile.password.title' }),
+ dialogUpdateUser: byRole('dialog', { name: 'users.update_user' }),
+ dialogCreateUser: byRole('dialog', { name: 'users.create_user' }),
+ dialogDeactivateUser: byRole('dialog', { name: 'users.deactivate_user' }),
+
+ infoManageMode: byText(/users\.page\.managed_description/),
+ description: byText('users.page.description'),
+ deleteUserAlert: byText('delete-user-warning'),
+
+ searchInput: byRole('searchbox', { name: 'search.search_by_login_or_name' }),
+ activityFilter: byRole('combobox', { name: 'users.activity_filter.label' }),
loginInput: byRole('textbox', { name: /login/ }),
userNameInput: byRole('textbox', { name: /name/ }),
+ emailInput: byRole('textbox', { name: /email/ }),
passwordInput: byLabelText(/password/),
- scmAddButton: byRole('button', { name: 'add_verb' }),
- createUserDialogButton: byRole('button', { name: 'create' }),
dialogSCMInputs: byRole('textbox', { name: /users.create_user.scm_account/ }),
dialogSCMInput: (value?: string) =>
byRole('textbox', { name: `users.create_user.scm_account_${value ? `x.${value}` : 'new'}` }),
- deleteSCMButton: (value?: string) =>
- byRole('button', {
- name: `remove_x.users.create_user.scm_account_${value ? `x.${value}` : 'new'}`,
- }),
- jackRow: byRole('row', { name: /Jack/ }),
+ oldPassword: byLabelText('my_profile.password.old', { selector: 'input', exact: false }),
+ newPassword: byLabelText('my_profile.password.new', { selector: 'input', exact: false }),
+ confirmPassword: byLabelText('my_profile.password.confirm', { selector: 'input', exact: false }),
+ tokenNameInput: byRole('textbox', { name: 'users.tokens.name' }),
+ deleteUserCheckbox: byRole('checkbox', { name: 'users.delete_user' }),
};
+beforeEach(() => {
+ tokenHandler.reset();
+ userHandler.reset();
+ componentsHandler.reset();
+ settingsHandler.reset();
+});
+
describe('different filters combinations', () => {
beforeAll(() => {
jest.useFakeTimers({
it('should display all users with default filters', async () => {
renderUsersApp();
- expect(await ui.userRows.findAll()).toHaveLength(6);
+ await act(async () => expect(await ui.userRows.findAll()).toHaveLength(6));
});
it('should display users filtered with text search', async () => {
+ const user = userEvent.setup();
renderUsersApp();
- await userEvent.type(await ui.searchInput.find(), 'ar');
+ await act(async () => user.type(await ui.searchInput.find(), 'ar'));
expect(await ui.userRows.findAll()).toHaveLength(2);
expect(ui.bobRow.get()).toBeInTheDocument();
});
it('should display local active SonarLint users', async () => {
+ const user = userEvent.setup();
renderUsersApp();
- await userEvent.click(await ui.localFilter.find());
+ await act(async () => user.click(await ui.localFilter.find()));
await act(async () => {
await selectEvent.select(
ui.activityFilter.get(),
});
it('should display managed active SonarQube users', async () => {
+ const user = userEvent.setup();
renderUsersApp();
- await userEvent.click(await ui.managedFilter.find());
+ await act(async () => user.click(await ui.managedFilter.find()));
await act(async () => {
await selectEvent.select(
ui.activityFilter.get(),
});
it('should display all inactive users', async () => {
+ const user = userEvent.setup();
renderUsersApp();
- await userEvent.click(await ui.allFilter.find());
+ await act(async () => user.click(await ui.allFilter.find()));
await act(async () => {
await selectEvent.select(ui.activityFilter.get(), 'users.activity_filter.inactive_users');
});
describe('in non managed mode', () => {
beforeEach(() => {
- handler.setIsManaged(false);
- });
-
- afterAll(() => {
- handler.reset();
+ userHandler.setIsManaged(false);
});
it('should allow the creation of user', async () => {
+ const user = userEvent.setup();
renderUsersApp();
- expect(await ui.description.find()).toBeInTheDocument();
+ await act(async () => expect(await ui.description.find()).toBeInTheDocument());
expect(ui.createUserButton.get()).toBeEnabled();
- await userEvent.click(ui.createUserButton.get());
+ await user.click(ui.createUserButton.get());
+
+ expect(await ui.dialogCreateUser.find()).toBeInTheDocument();
- await userEvent.type(ui.loginInput.get(), 'Login');
- await userEvent.type(ui.userNameInput.get(), 'Jack');
- await userEvent.type(ui.passwordInput.get(), 'Password');
+ await user.type(ui.loginInput.get(), 'Login');
+ await user.type(ui.userNameInput.get(), 'Jack');
+ await user.type(ui.passwordInput.get(), 'Password');
// Add SCM account
expect(ui.dialogSCMInputs.queryAll()).toHaveLength(0);
- await userEvent.click(ui.scmAddButton.get());
+ await user.click(ui.scmAddButton.get());
expect(ui.dialogSCMInputs.getAll()).toHaveLength(1);
- await userEvent.type(ui.dialogSCMInput().get(), 'SCM');
+ await user.type(ui.dialogSCMInput().get(), 'SCM');
expect(ui.dialogSCMInput('SCM').get()).toBeInTheDocument();
+ // Clear input to get an error on save
+ await user.clear(ui.dialogSCMInput('SCM').get());
+ await act(() => user.click(ui.createUserDialogButton.get()));
+ expect(ui.dialogCreateUser.get()).toBeInTheDocument();
+ expect(
+ await within(ui.dialogCreateUser.get()).findByText('Error: Empty SCM')
+ ).toBeInTheDocument();
// Remove SCM account
- await userEvent.click(ui.deleteSCMButton('SCM').get());
+ await user.click(ui.deleteSCMButton().get());
expect(ui.dialogSCMInputs.queryAll()).toHaveLength(0);
- await userEvent.click(ui.createUserDialogButton.get());
+ await act(() => user.click(ui.createUserDialogButton.get()));
expect(ui.jackRow.get()).toBeInTheDocument();
+ expect(ui.dialogCreateUser.query()).not.toBeInTheDocument();
});
- it("should be able to add/remove user's group", async () => {
+ it("should be able to see user's group", async () => {
+ const user = userEvent.setup();
renderUsersApp();
- expect(await ui.aliceUpdateGroupButton.find()).toBeInTheDocument();
+ await act(async () =>
+ expect(await within(await ui.aliceRow.find()).findByText('group1')).toBeInTheDocument()
+ );
+ expect(within(ui.aliceRow.get()).queryByText('group4')).not.toBeInTheDocument();
+ expect(within(ui.aliceRow.get()).getByText('more_x.2')).toBeInTheDocument();
+ await user.click(within(ui.aliceRow.get()).getByText('more_x.2'));
+ expect(within(ui.aliceRow.get()).queryByText('more_x.2')).not.toBeInTheDocument();
+ expect(await within(ui.aliceRow.get()).findByText('group4')).toBeInTheDocument();
expect(ui.bobUpdateGroupButton.get()).toBeInTheDocument();
});
- it('should be able to update / change password / deactivate a user', async () => {
- renderUsersApp();
-
- expect(await ui.aliceUpdateButton.find()).toBeInTheDocument();
- expect(ui.bobUpdateButton.get()).toBeInTheDocument();
- });
-
it('should render all users', async () => {
renderUsersApp();
- expect(await ui.aliceRow.find()).toBeInTheDocument();
+ await act(async () => expect(await ui.aliceRow.find()).toBeInTheDocument());
expect(ui.bobRow.get()).toBeInTheDocument();
expect(ui.aliceRowWithLocalBadge.query()).not.toBeInTheDocument();
});
const user = userEvent.setup();
renderUsersApp();
- expect(await ui.aliceRow.find()).toBeInTheDocument();
+ await act(async () => expect(await ui.aliceRow.find()).toBeInTheDocument());
expect(ui.bobRow.get()).toBeInTheDocument();
- expect(ui.userRows.getAll()).toHaveLength(7);
+ expect(ui.userRows.getAll()).toHaveLength(6);
await act(async () => {
await user.click(await ui.showMore.find());
});
- expect(ui.userRows.getAll()).toHaveLength(9);
+ expect(ui.userRows.getAll()).toHaveLength(8);
+ });
+
+ it('should be able to edit the groups of a user', async () => {
+ const user = userEvent.setup();
+ renderUsersApp();
+
+ await act(async () =>
+ user.click(
+ await within(await ui.aliceRow.find()).findByRole('button', {
+ name: 'users.update_users_groups.alice.merveille',
+ })
+ )
+ );
+ expect(await ui.dialogGroups.find()).toBeInTheDocument();
+
+ expect(ui.getGroups()).toHaveLength(2);
+
+ await user.click(await ui.allFilter.find());
+ expect(ui.getGroups()).toHaveLength(3);
+
+ await user.click(ui.unselectedFilter.get());
+ expect(ui.reloadButton.query()).not.toBeInTheDocument();
+ await user.click(ui.getGroups()[0]);
+ expect(await ui.reloadButton.find()).toBeInTheDocument();
+
+ await user.click(ui.selectedFilter.get());
+ expect(ui.getGroups()).toHaveLength(3);
+ expect(ui.reloadButton.query()).not.toBeInTheDocument();
+ await user.click(ui.getGroups()[1]);
+ expect(await ui.reloadButton.find()).toBeInTheDocument();
+ await user.click(ui.reloadButton.get());
+ expect(ui.getGroups()).toHaveLength(2);
+
+ await user.type(within(ui.dialogGroups.get()).getByRole('searchbox'), '3');
+
+ expect(ui.getGroups()).toHaveLength(1);
+
+ await act(() => user.click(ui.doneButton.get()));
+ expect(ui.dialogGroups.query()).not.toBeInTheDocument();
+ });
+
+ it('should update user', async () => {
+ const user = userEvent.setup();
+ renderUsersApp();
+
+ await act(async () =>
+ user.click(
+ await within(await ui.aliceRow.find()).findByRole('button', {
+ name: 'users.manage_user.alice.merveille',
+ })
+ )
+ );
+ await user.click(
+ await within(ui.aliceRow.get()).findByRole('button', { name: 'update_details' })
+ );
+ expect(await ui.dialogUpdateUser.find()).toBeInTheDocument();
+
+ expect(ui.userNameInput.get()).toHaveValue('Alice Merveille');
+ expect(ui.emailInput.get()).toHaveValue('');
+ await user.type(ui.userNameInput.get(), '1');
+ await user.type(ui.emailInput.get(), 'test@test.com');
+ await act(() => user.click(ui.updateButton.get()));
+ expect(ui.dialogUpdateUser.query()).not.toBeInTheDocument();
+ expect(await screen.findByText('Alice Merveille1')).toBeInTheDocument();
+ expect(await screen.findByText('test@test.com')).toBeInTheDocument();
+ });
+
+ it('should deactivate user', async () => {
+ const user = userEvent.setup();
+ renderUsersApp();
+
+ await act(async () =>
+ user.click(
+ await within(await ui.aliceRow.find()).findByRole('button', {
+ name: 'users.manage_user.alice.merveille',
+ })
+ )
+ );
+ await user.click(
+ await within(ui.aliceRow.get()).findByRole('button', { name: 'users.deactivate' })
+ );
+ expect(await ui.dialogDeactivateUser.find()).toBeInTheDocument();
+ expect(ui.deleteUserAlert.query()).not.toBeInTheDocument();
+ await user.click(ui.deleteUserCheckbox.get());
+ expect(await ui.deleteUserAlert.find()).toBeInTheDocument();
+
+ await act(() =>
+ user.click(
+ within(ui.dialogDeactivateUser.get()).getByRole('button', { name: 'users.deactivate' })
+ )
+ );
+ expect(ui.aliceRow.query()).not.toBeInTheDocument();
+ });
+
+ it('should change a password', async () => {
+ const user = userEvent.setup();
+ const currentUser = mockLoggedInUser({ login: 'alice.merveille' });
+ renderUsersApp(currentUser);
+
+ await act(async () =>
+ user.click(
+ await within(await ui.aliceRow.find()).findByRole('button', {
+ name: 'users.manage_user.alice.merveille',
+ })
+ )
+ );
+ await user.click(
+ await within(ui.aliceRow.get()).findByRole('button', { name: 'my_profile.password.title' })
+ );
+ expect(await ui.dialogPasswords.find()).toBeInTheDocument();
+
+ expect(await ui.oldPassword.find()).toBeInTheDocument();
+
+ expect(ui.changeButton.get()).toBeDisabled();
+
+ await user.type(ui.oldPassword.get(), '123');
+ await user.type(ui.newPassword.get(), '1234');
+ await user.type(ui.confirmPassword.get(), '1234');
+
+ expect(ui.changeButton.get()).toBeEnabled();
+ expect(
+ screen.queryByText(`user.${ChangePasswordResults.OldPasswordIncorrect}`)
+ ).not.toBeInTheDocument();
+ await user.click(ui.changeButton.get());
+ expect(
+ await within(ui.dialogPasswords.get()).findByText(
+ `user.${ChangePasswordResults.OldPasswordIncorrect}`
+ )
+ ).toBeInTheDocument();
+
+ await user.clear(ui.oldPassword.get());
+ await user.clear(ui.newPassword.get());
+ await user.clear(ui.confirmPassword.get());
+ await user.type(ui.oldPassword.get(), 'test');
+ await user.type(ui.newPassword.get(), 'test');
+ await user.type(ui.confirmPassword.get(), 'test');
+
+ expect(
+ screen.queryByText(`user.${ChangePasswordResults.NewPasswordSameAsOld}`)
+ ).not.toBeInTheDocument();
+ await user.click(ui.changeButton.get());
+ expect(
+ await screen.findByText(`user.${ChangePasswordResults.NewPasswordSameAsOld}`)
+ ).toBeInTheDocument();
+
+ await user.clear(ui.newPassword.get());
+ await user.clear(ui.confirmPassword.get());
+ await user.type(ui.newPassword.get(), 'test2');
+ await user.type(ui.confirmPassword.get(), 'test2');
+
+ await user.click(ui.changeButton.get());
+
+ expect(ui.dialogPasswords.query()).not.toBeInTheDocument();
});
});
describe('in manage mode', () => {
beforeEach(() => {
- handler.setIsManaged(true);
+ userHandler.setIsManaged(true);
});
it('should not be able to create a user"', async () => {
renderUsersApp();
- expect(await ui.infoManageMode.find()).toBeInTheDocument();
+ await act(async () => expect(await ui.infoManageMode.find()).toBeInTheDocument());
expect(ui.createUserButton.get()).toBeDisabled();
});
it("should not be able to add/remove a user's group", async () => {
renderUsersApp();
- expect(await ui.aliceRowWithLocalBadge.find()).toBeInTheDocument();
+ await act(async () => expect(await ui.aliceRowWithLocalBadge.find()).toBeInTheDocument());
expect(ui.aliceUpdateGroupButton.query()).not.toBeInTheDocument();
expect(ui.bobRow.get()).toBeInTheDocument();
expect(ui.bobUpdateGroupButton.query()).not.toBeInTheDocument();
it('should not be able to update / change password / deactivate a managed user', async () => {
renderUsersApp();
- expect(await ui.bobRow.find()).toBeInTheDocument();
+ await act(async () => expect(await ui.bobRow.find()).toBeInTheDocument());
expect(ui.bobUpdateButton.query()).not.toBeInTheDocument();
});
const user = userEvent.setup();
renderUsersApp();
- expect(await ui.aliceRowWithLocalBadge.find()).toBeInTheDocument();
+ await act(async () => expect(await ui.aliceRowWithLocalBadge.find()).toBeInTheDocument());
await user.click(ui.aliceUpdateButton.get());
expect(await ui.alicedDeactivateButton.find()).toBeInTheDocument();
});
it('should render list of all users', async () => {
renderUsersApp();
- expect(await ui.allFilter.find()).toBeInTheDocument();
+ await act(async () => expect(await ui.allFilter.find()).toBeInTheDocument());
expect(ui.aliceRowWithLocalBadge.get()).toBeInTheDocument();
expect(ui.bobRow.get()).toBeInTheDocument();
const user = userEvent.setup();
renderUsersApp();
- expect(await ui.aliceRowWithLocalBadge.find()).toBeInTheDocument();
+ await act(async () => expect(await ui.aliceRowWithLocalBadge.find()).toBeInTheDocument());
- await act(async () => {
- await user.click(await ui.managedFilter.find());
- });
+ await act(async () => user.click(await ui.managedFilter.find()));
expect(await ui.bobRow.find()).toBeInTheDocument();
expect(ui.aliceRowWithLocalBadge.query()).not.toBeInTheDocument();
await user.click(await ui.localFilter.find());
});
- expect(await ui.aliceRowWithLocalBadge.find()).toBeInTheDocument();
+ expect(ui.aliceRowWithLocalBadge.get()).toBeInTheDocument();
expect(ui.bobRow.query()).not.toBeInTheDocument();
});
+
+ it('should be able to change tokens of a user', async () => {
+ const user = userEvent.setup();
+ renderUsersApp();
+
+ await act(async () =>
+ user.click(
+ await within(await ui.aliceRow.find()).findByRole('button', {
+ name: 'users.update_tokens_for_x.Alice Merveille',
+ })
+ )
+ );
+ expect(await ui.dialogTokens.find()).toBeInTheDocument();
+
+ const getTokensList = () => within(ui.dialogTokens.get()).getAllByRole('row');
+
+ expect(getTokensList()).toHaveLength(3);
+
+ await user.type(ui.tokenNameInput.get(), 'test');
+ await user.click(ui.generateButton.get());
+
+ // Not deleted because there is already token with name test
+ expect(screen.queryByText('users.tokens.new_token_created.test')).not.toBeInTheDocument();
+ expect(getTokensList()).toHaveLength(3);
+
+ expect(ui.sureButton.query()).not.toBeInTheDocument();
+ await user.click(ui.revokeButton.getAll()[1]);
+ expect(await ui.sureButton.find()).toBeInTheDocument();
+ await act(() => user.click(ui.sureButton.get()));
+
+ expect(getTokensList()).toHaveLength(2);
+
+ await act(() => user.click(ui.generateButton.get()));
+ expect(getTokensList()).toHaveLength(3);
+ expect(await screen.findByText('users.tokens.new_token_created.test')).toBeInTheDocument();
+
+ await user.click(ui.doneButton.get());
+ expect(ui.dialogTokens.query()).not.toBeInTheDocument();
+ });
+});
+
+it('should render external identity Providers', async () => {
+ renderUsersApp();
+
+ await act(async () => expect(await ui.charlieRow.find()).toHaveTextContent(/ExternalTest/));
+ expect(await ui.denisRow.find()).toHaveTextContent(/test2: UnknownExternalProvider/);
});
-function renderUsersApp() {
- return renderApp('admin/users', <UsersApp />);
+function renderUsersApp(currentUser?: CurrentUser) {
+ // eslint-disable-next-line testing-library/no-unnecessary-act
+ return renderApp('admin/users', <UsersApp />, { currentUser: mockCurrentUser(currentUser) });
}
+++ /dev/null
-/*
- * 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 * as React from 'react';
-import { deactivateUser } from '../../../../api/users';
-import { mockUser } from '../../../../helpers/testMocks';
-import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import { UserActive } from '../../../../types/users';
-import DeactivateForm from '../DeactivateForm';
-
-jest.mock('../../../../api/users', () => ({
- deactivateUser: jest.fn().mockResolvedValue({}),
-}));
-
-it('should deactivate user with anonymize set to true', () => {
- const user = mockUser() as UserActive;
- renderDeactivateForm(user);
-
- screen.getByRole('checkbox').click();
- expect(screen.getByRole('alert')).toBeInTheDocument();
-
- screen.getByRole('button', { name: 'users.deactivate' }).click();
- expect(deactivateUser).toHaveBeenCalledWith({ login: user.login, anonymize: true });
-});
-
-function renderDeactivateForm(user: UserActive) {
- return renderComponent(
- <DeactivateForm onClose={jest.fn()} onUpdateUsers={jest.fn()} user={user} />
- );
-}
+++ /dev/null
-/*
- * 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 { getUserGroups } from '../../../../api/users';
-import { addUserToGroup, removeUserFromGroup } from '../../../../api/user_groups';
-import SelectList, { SelectListFilter } from '../../../../components/controls/SelectList';
-import { mockUser } from '../../../../helpers/testMocks';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
-import GroupsForm from '../GroupsForm';
-
-const user = mockUser();
-
-jest.mock('../../../../api/users', () => ({
- getUserGroups: jest.fn().mockResolvedValue({
- paging: { pageIndex: 1, pageSize: 10, total: 1 },
- groups: [
- {
- id: 1001,
- name: 'test1',
- description: 'test1',
- selected: true,
- },
- {
- id: 1002,
- name: 'test2',
- description: 'test2',
- selected: true,
- },
- {
- id: 1003,
- name: 'test3',
- description: 'test3',
- selected: false,
- },
- ],
- }),
-}));
-
-jest.mock('../../../../api/user_groups', () => ({
- addUserToGroup: jest.fn().mockResolvedValue({}),
- removeUserFromGroup: jest.fn().mockResolvedValue({}),
-}));
-
-beforeEach(() => {
- jest.clearAllMocks();
-});
-
-it('should render correctly', async () => {
- const wrapper = shallowRender();
- wrapper.find(SelectList).props().onSearch({
- query: '',
- filter: SelectListFilter.Selected,
- page: 1,
- pageSize: 100,
- });
- await waitAndUpdate(wrapper);
-
- expect(wrapper.instance().mounted).toBe(true);
- expect(wrapper).toMatchSnapshot();
- expect(wrapper.instance().renderElement('test1')).toMatchSnapshot();
- expect(wrapper.instance().renderElement('test_foo')).toMatchSnapshot();
-
- expect(getUserGroups).toHaveBeenCalledWith(
- expect.objectContaining({
- login: user.login,
- p: 1,
- ps: 100,
- q: undefined,
- selected: SelectListFilter.Selected,
- })
- );
- expect(wrapper.state().needToReload).toBe(false);
-
- wrapper.instance().componentWillUnmount();
- expect(wrapper.instance().mounted).toBe(false);
-});
-
-it('should handle selection properly', async () => {
- const wrapper = shallowRender();
- wrapper.instance().handleSelect('toto');
- await waitAndUpdate(wrapper);
-
- expect(addUserToGroup).toHaveBeenCalledWith(
- expect.objectContaining({
- login: user.login,
- name: 'toto',
- })
- );
- expect(wrapper.state().needToReload).toBe(true);
-});
-
-it('should handle deselection properly', async () => {
- const wrapper = shallowRender();
- wrapper.instance().handleUnselect('tata');
- await waitAndUpdate(wrapper);
-
- expect(removeUserFromGroup).toHaveBeenCalledWith(
- expect.objectContaining({
- login: user.login,
- name: 'tata',
- })
- );
- expect(wrapper.state().needToReload).toBe(true);
-});
-
-function shallowRender(props: Partial<GroupsForm['props']> = {}) {
- return shallow<GroupsForm>(
- <GroupsForm onClose={jest.fn()} onUpdateUsers={jest.fn()} user={user} {...props} />
- );
-}
+++ /dev/null
-/*
- * 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 { changePassword } from '../../../../api/users';
-import { mockUser } from '../../../../helpers/testMocks';
-import { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils';
-import { ChangePasswordResults } from '../../../../types/users';
-import PasswordForm from '../PasswordForm';
-
-const password = 'new password asdf';
-
-jest.mock('../../../../api/users', () => ({
- changePassword: jest.fn(() => Promise.resolve()),
-}));
-
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should handle password change', async () => {
- const onClose = jest.fn();
- const wrapper = shallowRender({ onClose });
-
- wrapper.setState({ newPassword: password, confirmPassword: password });
- wrapper.instance().handleChangePassword(mockEvent({ preventDefault: jest.fn() }));
-
- await waitAndUpdate(wrapper);
-
- expect(onClose).toHaveBeenCalled();
-});
-
-it('should handle password change error when new password is same as old', async () => {
- const wrapper = shallowRender();
-
- jest.mocked(changePassword).mockRejectedValue(ChangePasswordResults.NewPasswordSameAsOld);
- wrapper.setState({ newPassword: password, confirmPassword: password });
- wrapper.instance().mounted = true;
- wrapper.instance().handleChangePassword(mockEvent({ preventDefault: jest.fn() }));
-
- await waitAndUpdate(wrapper);
-
- expect(wrapper.state().errorTranslationKey).toBe('user.new_password_same_as_old');
-});
-
-it('should handle password change error when old password is incorrect', async () => {
- const wrapper = shallowRender();
-
- jest.mocked(changePassword).mockRejectedValue(ChangePasswordResults.OldPasswordIncorrect);
-
- wrapper.setState({ newPassword: password, confirmPassword: password });
- wrapper.instance().mounted = true;
- wrapper.instance().handleChangePassword(mockEvent({ preventDefault: jest.fn() }));
-
- await waitAndUpdate(wrapper);
-
- expect(wrapper.state().errorTranslationKey).toBe('user.old_password_incorrect');
-});
-
-it('should handle form changes', () => {
- const wrapper = shallowRender();
-
- wrapper.instance().handleConfirmPasswordChange(mockEvent({ currentTarget: { value: 'pwd' } }));
- expect(wrapper.state().confirmPassword).toBe('pwd');
-
- wrapper.instance().handleNewPasswordChange(mockEvent({ currentTarget: { value: 'pwd' } }));
- expect(wrapper.state().newPassword).toBe('pwd');
-
- wrapper.instance().handleOldPasswordChange(mockEvent({ currentTarget: { value: 'pwd' } }));
- expect(wrapper.state().oldPassword).toBe('pwd');
-});
-
-function shallowRender(props: Partial<PasswordForm['props']> = {}) {
- return shallow<PasswordForm>(
- <PasswordForm isCurrentUser={true} onClose={jest.fn()} user={mockUser()} {...props} />
- );
-}
+++ /dev/null
-/*
- * 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 { revokeToken } from '../../../../api/user-tokens';
-import { mockUserToken } from '../../../../helpers/mocks/token';
-import { click, waitAndUpdate } from '../../../../helpers/testUtils';
-import TokensFormItem from '../TokensFormItem';
-
-jest.mock('../../../../components/intl/DateFormatter');
-jest.mock('../../../../components/intl/DateFromNow');
-jest.mock('../../../../components/intl/DateTimeFormatter');
-
-jest.mock('../../../../api/user-tokens', () => ({
- revokeToken: jest.fn().mockResolvedValue(undefined),
-}));
-
-const userToken = mockUserToken({
- name: 'foo',
- createdAt: '2019-01-15T15:06:33+0100',
- lastConnectionDate: '2019-01-18T15:06:33+0100',
-});
-
-beforeEach(() => {
- (revokeToken as jest.Mock).mockClear();
-});
-
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
- expect(shallowRender({ deleteConfirmation: 'modal' })).toMatchSnapshot();
-});
-
-it('should revoke the token using inline confirmation', async () => {
- const onRevokeToken = jest.fn();
- const wrapper = shallowRender({ deleteConfirmation: 'inline', onRevokeToken });
- expect(wrapper.find('Button')).toMatchSnapshot();
- click(wrapper.find('Button'));
- expect(wrapper.find('Button')).toMatchSnapshot();
- click(wrapper.find('Button'));
- expect(wrapper.find('DeferredSpinner').prop('loading')).toBe(true);
- await waitAndUpdate(wrapper);
- expect(revokeToken).toHaveBeenCalledWith({ login: 'luke', name: 'foo' });
- expect(onRevokeToken).toHaveBeenCalledWith(userToken);
-});
-
-it('should revoke the token using modal confirmation', async () => {
- const onRevokeToken = jest.fn();
- const wrapper = shallowRender({ deleteConfirmation: 'modal', onRevokeToken });
- wrapper.find('ConfirmButton').prop<Function>('onConfirm')();
- expect(revokeToken).toHaveBeenCalledWith({ login: 'luke', name: 'foo' });
- await waitAndUpdate(wrapper);
- expect(onRevokeToken).toHaveBeenCalledWith(userToken);
-});
-
-function shallowRender(props: Partial<TokensFormItem['props']> = {}) {
- return shallow(
- <TokensFormItem
- deleteConfirmation="inline"
- login="luke"
- onRevokeToken={jest.fn()}
- token={userToken}
- {...props}
- />
- );
-}
+++ /dev/null
-/*
- * 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 { mockUser } from '../../../../helpers/testMocks';
-import TokensFormModal from '../TokensFormModal';
-
-it('should render correctly', () => {
- expect(
- shallow(<TokensFormModal onClose={jest.fn()} updateTokensCount={jest.fn()} user={mockUser()} />)
- ).toMatchSnapshot();
-});
+++ /dev/null
-/*
- * 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 UserActions from '../UserActions';
-
-const user = {
- login: 'obi',
- name: 'One',
- active: true,
- scmAccounts: [],
- local: false,
- managed: false,
-};
-
-it('should render correctly', () => {
- expect(getWrapper()).toMatchSnapshot();
-});
-
-it('should open the update form', () => {
- const wrapper = getWrapper();
- click(wrapper.find('.js-user-update'));
- expect(wrapper.first().find('UserForm').exists()).toBe(true);
-});
-
-it('should open the password form', () => {
- const wrapper = getWrapper({ user: { ...user, local: true } });
- click(wrapper.find('.js-user-change-password'));
- expect(wrapper.first().find('PasswordForm').exists()).toBe(true);
-});
-
-it('should open the deactivate form', () => {
- const wrapper = getWrapper();
- click(wrapper.find('.js-user-deactivate'));
- expect(wrapper.first().find('DeactivateForm').exists()).toBe(true);
-});
-
-function getWrapper(props = {}) {
- return shallow(
- <UserActions
- isCurrentUser={false}
- onUpdateUsers={jest.fn()}
- user={user}
- manageProvider={undefined}
- {...props}
- />
- );
-}
+++ /dev/null
-/*
- * 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 { createUser, updateUser } from '../../../../api/users';
-import { Alert } from '../../../../components/ui/Alert';
-import { mockUser } from '../../../../helpers/testMocks';
-import { submit, waitAndUpdate } from '../../../../helpers/testUtils';
-import UserForm from '../UserForm';
-
-jest.mock('../../../../api/users', () => ({
- createUser: jest.fn().mockResolvedValue({}),
- updateUser: jest.fn().mockResolvedValue({}),
-}));
-
-beforeEach(() => {
- jest.clearAllMocks();
-});
-
-it('should render correctly', () => {
- expect(shallowRender().dive()).toMatchSnapshot();
- expect(shallowRender({ user: undefined }).dive()).toMatchSnapshot();
-});
-
-it('should correctly show errors', async () => {
- const response = new Response(null, { status: 400 });
- response.json = jest.fn().mockRejectedValue(undefined);
-
- (updateUser as jest.Mock).mockRejectedValue(response);
-
- const wrapper = shallowRender();
- submit(wrapper.dive().find('form'));
- await waitAndUpdate(wrapper);
-
- expect(wrapper.dive().find(Alert).children().text()).toMatch('default_error_message');
-});
-
-it('should correctly disable name and email fields for non-local users', () => {
- const wrapper = shallowRender({ user: mockUser({ local: false }) }).dive();
- expect(wrapper.find('#create-user-name').prop('disabled')).toBe(true);
- expect(wrapper.find('#create-user-email').prop('disabled')).toBe(true);
- expect(wrapper.find('Alert').exists()).toBe(true);
- expect(wrapper.find(Alert).children().text()).toMatch('users.cannot_update_delegated_user');
-});
-
-it('should correctly create a new user', () => {
- const email = 'foo@bar.ch';
- const login = 'foo';
- const name = 'Foo';
- const password = 'bar';
- const scmAccounts = ['gh', 'gh', 'bitbucket'];
- const wrapper = shallowRender({ user: undefined });
-
- wrapper.setState({ email, login, name, password, scmAccounts });
-
- submit(wrapper.dive().find('form'));
-
- expect(createUser).toHaveBeenCalledWith({
- email,
- login,
- name,
- password,
- scmAccount: ['gh', 'gh', 'bitbucket'],
- });
-});
-
-it('should correctly update a local user', () => {
- const email = 'foo@bar.ch';
- const login = 'foo';
- const name = 'Foo';
- const scmAccounts = ['gh', 'gh', 'bitbucket'];
- const wrapper = shallowRender({ user: mockUser({ email, login, name, scmAccounts }) }).dive();
-
- submit(wrapper.find('form'));
-
- expect(updateUser).toHaveBeenCalledWith({
- email,
- login,
- name,
- scmAccount: ['gh', 'gh', 'bitbucket'],
- });
-});
-
-it('should correctly update a non-local user', () => {
- const email = 'foo@bar.ch';
- const login = 'foo';
- const name = 'Foo';
- const scmAccounts = ['gh', 'bitbucket'];
- const wrapper = shallowRender({
- user: mockUser({ email, local: false, login, name, scmAccounts }),
- }).dive();
-
- submit(wrapper.find('form'));
-
- expect(updateUser).toHaveBeenCalledWith(
- expect.not.objectContaining({
- email,
- name,
- })
- );
-});
-
-function shallowRender(props: Partial<UserForm['props']> = {}) {
- return shallow(
- <UserForm onClose={jest.fn()} onUpdateUsers={jest.fn()} user={mockUser()} {...props} />
- );
-}
+++ /dev/null
-/*
- * 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 UserGroups from '../UserGroups';
-
-const user = {
- login: 'obi',
- name: 'One',
- active: true,
- scmAccounts: [],
- local: false,
- managed: false,
-};
-
-const groups = ['foo', 'bar', 'baz', 'plop'];
-
-it('should render correctly', () => {
- expect(getWrapper()).toMatchSnapshot();
-});
-
-it('should show all groups', () => {
- const wrapper = getWrapper();
- expect(wrapper.find('li')).toHaveLength(3);
- click(wrapper.find('.js-user-more-groups'));
- expect(wrapper.find('li')).toHaveLength(5);
-});
-
-it('should open the groups form', () => {
- const wrapper = getWrapper();
- click(wrapper.find('.js-user-groups'));
- expect(wrapper.find('GroupsForm').exists()).toBe(true);
-});
-
-function getWrapper(props = {}) {
- return shallow(
- <UserGroups
- groups={groups}
- onUpdateUsers={jest.fn()}
- user={user}
- manageProvider={undefined}
- {...props}
- />
- );
-}
+++ /dev/null
-/*
- * 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 { User } from '../../../../types/users';
-import UserListItem, { UserListItemProps } from '../UserListItem';
-
-jest.mock('../../../../components/intl/DateFromNow');
-jest.mock('../../../../components/intl/DateTimeFormatter');
-
-const user: User = {
- active: true,
- lastConnectionDate: '2019-01-18T15:06:33+0100',
- sonarLintLastConnectionDate: '2019-01-16T15:06:33+0100',
- local: false,
- login: 'obi',
- name: 'One',
- scmAccounts: [],
- managed: false,
-};
-
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should render correctly without last connection date', () => {
- expect(shallowRender({})).toMatchSnapshot();
-});
-
-it('should open the correct forms', () => {
- const wrapper = shallowRender();
- click(wrapper.find('.js-user-tokens'));
- expect(wrapper.find('TokensFormModal').exists()).toBe(true);
-});
-
-function shallowRender(props: Partial<UserListItemProps> = {}) {
- return shallow(
- <UserListItem
- isCurrentUser={false}
- onUpdateUsers={jest.fn()}
- updateTokensCount={jest.fn()}
- user={user}
- manageProvider={undefined}
- {...props}
- />
- );
-}
+++ /dev/null
-/*
- * 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 UserListItemIdentity, { ExternalProvider, Props } from '../UserListItemIdentity';
-
-describe('#UserListItemIdentity', () => {
- it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
- });
-
- function shallowRender(props: Partial<Props> = {}) {
- return shallow(
- <UserListItemIdentity
- identityProvider={{
- backgroundColor: 'blue',
- iconPath: 'icon/path',
- key: 'foo',
- name: 'Foo Provider',
- }}
- user={{
- active: true,
- email: 'obi.one@empire',
- externalProvider: 'foo',
- lastConnectionDate: '2019-01-18T15:06:33+0100',
- local: false,
- login: 'obi',
- name: 'One',
- scmAccounts: [],
- managed: false,
- }}
- {...props}
- />
- );
- }
-});
-
-describe('#ExternalProvider', () => {
- it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
- });
-
- it('should render the user external provider and identity', () => {
- expect(shallowRender({ identityProvider: undefined })).toMatchSnapshot();
- });
-
- function shallowRender(props: Partial<Props> = {}) {
- return shallow(
- <ExternalProvider
- identityProvider={{
- backgroundColor: 'blue',
- iconPath: 'icon/path',
- key: 'foo',
- name: 'Foo Provider',
- }}
- user={{
- active: true,
- email: 'obi.one@empire',
- externalProvider: 'foo',
- lastConnectionDate: '2019-01-18T15:06:33+0100',
- local: false,
- login: 'obi',
- name: 'One',
- scmAccounts: [],
- managed: false,
- }}
- {...props}
- />
- );
- }
-});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Modal
- contentLabel="users.update_groups"
- onRequestClose={[Function]}
->
- <div
- className="modal-head"
- >
- <h2>
- users.update_groups
- </h2>
- </div>
- <div
- className="modal-body modal-container"
- >
- <SelectList
- elements={
- [
- "test1",
- "test2",
- "test3",
- ]
- }
- elementsTotalCount={1}
- needToReload={false}
- onSearch={[Function]}
- onSelect={[Function]}
- onUnselect={[Function]}
- renderElement={[Function]}
- selectedElements={
- [
- "test1",
- "test2",
- ]
- }
- withPaging={true}
- />
- </div>
- <footer
- className="modal-foot"
- >
- <ResetButtonLink
- onClick={[Function]}
- >
- done
- </ResetButtonLink>
- </footer>
-</Modal>
-`;
-
-exports[`should render correctly 2`] = `
-<div
- className="select-list-list-item"
->
- <React.Fragment>
- test1
- <br />
- <span
- className="note"
- >
- test1
- </span>
- </React.Fragment>
-</div>
-`;
-
-exports[`should render correctly 3`] = `
-<div
- className="select-list-list-item"
->
- test_foo
-</div>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Modal
- contentLabel="my_profile.password.title"
- onRequestClose={[MockFunction]}
- size="small"
->
- <form
- autoComplete="off"
- id="user-password-form"
- onSubmit={[Function]}
- >
- <header
- className="modal-head"
- >
- <h2>
- my_profile.password.title
- </h2>
- </header>
- <div
- className="modal-body"
- >
- <MandatoryFieldsExplanation
- className="modal-field"
- />
- <div
- className="modal-field"
- >
- <label
- htmlFor="old-user-password"
- >
- my_profile.password.old
- <MandatoryFieldMarker />
- </label>
- <input
- className="hidden"
- name="old-password-fake"
- type="password"
- />
- <input
- id="old-user-password"
- name="old-password"
- onChange={[Function]}
- required={true}
- type="password"
- value=""
- />
- </div>
- <div
- className="modal-field"
- >
- <label
- htmlFor="user-password"
- >
- my_profile.password.new
- <MandatoryFieldMarker />
- </label>
- <input
- className="hidden"
- name="password-fake"
- type="password"
- />
- <input
- id="user-password"
- name="password"
- onChange={[Function]}
- required={true}
- type="password"
- value=""
- />
- </div>
- <div
- className="modal-field"
- >
- <label
- htmlFor="confirm-user-password"
- >
- my_profile.password.confirm
- <MandatoryFieldMarker />
- </label>
- <input
- className="hidden"
- name="confirm-password-fake"
- type="password"
- />
- <input
- id="confirm-user-password"
- name="confirm-password"
- onChange={[Function]}
- required={true}
- type="password"
- value=""
- />
- </div>
- </div>
- <footer
- className="modal-foot"
- >
- <SubmitButton
- disabled={true}
- >
- change_verb
- </SubmitButton>
- <ResetButtonLink
- onClick={[MockFunction]}
- >
- cancel
- </ResetButtonLink>
- </footer>
- </form>
-</Modal>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tr>
- <td
- className="hide-overflow nowrap"
- title="foo"
- >
- foo
- </td>
- <td
- className="hide-overflow thin"
- title="users.tokens.USER_TOKEN"
- >
- users.tokens.USER_TOKEN.short
- </td>
- <td
- className="hide-overflow"
- />
- <td
- className="thin nowrap"
- >
- <DateFromNow
- date="2019-01-18T15:06:33+0100"
- hourPrecision={true}
- />
- </td>
- <td
- className="thin nowrap text-right"
- >
- <DateFormatter
- date="2019-01-15T15:06:33+0100"
- long={true}
- />
- </td>
- <td
- className="thin nowrap text-right"
- >
- –
- </td>
- <td
- className="thin nowrap text-right"
- >
- <Button
- className="button-red input-small"
- disabled={false}
- onClick={[Function]}
- >
- <DeferredSpinner
- className="little-spacer-right"
- loading={false}
- />
- users.tokens.revoke
- </Button>
- </td>
-</tr>
-`;
-
-exports[`should render correctly 2`] = `
-<tr>
- <td
- className="hide-overflow nowrap"
- title="foo"
- >
- foo
- </td>
- <td
- className="hide-overflow thin"
- title="users.tokens.USER_TOKEN"
- >
- users.tokens.USER_TOKEN.short
- </td>
- <td
- className="hide-overflow"
- />
- <td
- className="thin nowrap"
- >
- <DateFromNow
- date="2019-01-18T15:06:33+0100"
- hourPrecision={true}
- />
- </td>
- <td
- className="thin nowrap text-right"
- >
- <DateFormatter
- date="2019-01-15T15:06:33+0100"
- long={true}
- />
- </td>
- <td
- className="thin nowrap text-right"
- >
- –
- </td>
- <td
- className="thin nowrap text-right"
- >
- <ConfirmButton
- confirmButtonText="users.tokens.revoke_token"
- isDestructive={true}
- modalBody={
- <FormattedMessage
- defaultMessage="users.tokens.sure_X"
- id="users.tokens.sure_X"
- values={
- {
- "token": <strong>
- foo
- </strong>,
- }
- }
- />
- }
- modalHeader="users.tokens.revoke_token"
- onConfirm={[Function]}
- >
- <Component />
- </ConfirmButton>
- </td>
-</tr>
-`;
-
-exports[`should revoke the token using inline confirmation 1`] = `
-<Button
- className="button-red input-small"
- disabled={false}
- onClick={[Function]}
->
- <DeferredSpinner
- className="little-spacer-right"
- loading={false}
- />
- users.tokens.revoke
-</Button>
-`;
-
-exports[`should revoke the token using inline confirmation 2`] = `
-<Button
- className="button-red input-small"
- disabled={false}
- onClick={[Function]}
->
- <DeferredSpinner
- className="little-spacer-right"
- loading={false}
- />
- users.tokens.sure
-</Button>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Modal
- contentLabel="users.tokens"
- onRequestClose={[MockFunction]}
- size="large"
->
- <header
- className="modal-head"
- >
- <h2>
- <FormattedMessage
- defaultMessage="users.user_X_tokens"
- id="users.user_X_tokens"
- values={
- {
- "user": <em>
- John Doe
- </em>,
- }
- }
- />
- </h2>
- </header>
- <div
- className="modal-body modal-container"
- >
- <withCurrentUserContext(TokensForm)
- deleteConfirmation="inline"
- displayTokenTypeInput={false}
- login="john.doe"
- updateTokensCount={[MockFunction]}
- />
- </div>
- <footer
- className="modal-foot"
- >
- <ResetButtonLink
- onClick={[MockFunction]}
- >
- done
- </ResetButtonLink>
- </footer>
-</Modal>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Fragment>
- <ActionsDropdown
- label="users.manage_user.obi"
- >
- <ActionsDropdownItem
- className="js-user-update"
- onClick={[Function]}
- >
- update_details
- </ActionsDropdownItem>
- <ActionsDropdownDivider />
- <ActionsDropdownItem
- className="js-user-deactivate"
- destructive={true}
- onClick={[Function]}
- >
- users.deactivate
- </ActionsDropdownItem>
- </ActionsDropdown>
-</Fragment>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Modal
- contentLabel="users.update_user"
- onRequestClose={[MockFunction]}
- size="small"
->
- <form
- autoComplete="off"
- id="user-form"
- onSubmit={[Function]}
- >
- <header
- className="modal-head"
- >
- <h2>
- users.update_user
- </h2>
- </header>
- <div
- className="modal-body modal-container"
- >
- <MandatoryFieldsExplanation
- className="modal-field"
- />
- <div
- className="modal-field"
- >
- <label
- htmlFor="create-user-name"
- >
- name
- <MandatoryFieldMarker />
- </label>
- <input
- autoComplete="off"
- autoFocus={true}
- disabled={false}
- id="create-user-name"
- maxLength={200}
- name="name"
- onChange={[Function]}
- required={true}
- type="text"
- value="John Doe"
- />
- </div>
- <div
- className="modal-field"
- >
- <label
- htmlFor="create-user-email"
- >
- users.email
- </label>
- <input
- autoComplete="off"
- disabled={false}
- id="create-user-email"
- maxLength={100}
- name="email"
- onChange={[Function]}
- type="email"
- value=""
- />
- </div>
- <div
- className="modal-field"
- >
- <fieldset>
- <legend>
- my_profile.scm_accounts
- </legend>
- <div
- className="spacer-bottom"
- >
- <Button
- className="js-scm-account-add"
- onClick={[Function]}
- >
- add_verb
- </Button>
- </div>
- </fieldset>
- <p
- className="note"
- >
- user.login_or_email_used_as_scm_account
- </p>
- </div>
- </div>
- <footer
- className="modal-foot"
- >
- <SubmitButton
- disabled={false}
- >
- update_verb
- </SubmitButton>
- <ResetButtonLink
- onClick={[Function]}
- >
- cancel
- </ResetButtonLink>
- </footer>
- </form>
-</Modal>
-`;
-
-exports[`should render correctly 2`] = `
-<Modal
- contentLabel="users.create_user"
- onRequestClose={[MockFunction]}
- size="small"
->
- <form
- autoComplete="off"
- id="user-form"
- onSubmit={[Function]}
- >
- <header
- className="modal-head"
- >
- <h2>
- users.create_user
- </h2>
- </header>
- <div
- className="modal-body modal-container"
- >
- <MandatoryFieldsExplanation
- className="modal-field"
- />
- <div
- className="modal-field"
- >
- <label
- htmlFor="create-user-login"
- >
- login
- <MandatoryFieldMarker />
- </label>
- <input
- autoComplete="off"
- autoFocus={true}
- id="create-user-login"
- maxLength={255}
- minLength={3}
- name="login"
- onChange={[Function]}
- required={true}
- type="text"
- value=""
- />
- <p
- className="note"
- >
- users.minimum_x_characters.3
- </p>
- </div>
- <div
- className="modal-field"
- >
- <label
- htmlFor="create-user-name"
- >
- name
- <MandatoryFieldMarker />
- </label>
- <input
- autoComplete="off"
- autoFocus={false}
- id="create-user-name"
- maxLength={200}
- name="name"
- onChange={[Function]}
- required={true}
- type="text"
- value=""
- />
- </div>
- <div
- className="modal-field"
- >
- <label
- htmlFor="create-user-email"
- >
- users.email
- </label>
- <input
- autoComplete="off"
- id="create-user-email"
- maxLength={100}
- name="email"
- onChange={[Function]}
- type="email"
- value=""
- />
- </div>
- <div
- className="modal-field"
- >
- <label
- htmlFor="create-user-password"
- >
- password
- <MandatoryFieldMarker />
- </label>
- <input
- autoComplete="off"
- id="create-user-password"
- name="password"
- onChange={[Function]}
- required={true}
- type="password"
- value=""
- />
- </div>
- <div
- className="modal-field"
- >
- <fieldset>
- <legend>
- my_profile.scm_accounts
- </legend>
- <div
- className="spacer-bottom"
- >
- <Button
- className="js-scm-account-add"
- onClick={[Function]}
- >
- add_verb
- </Button>
- </div>
- </fieldset>
- <p
- className="note"
- >
- user.login_or_email_used_as_scm_account
- </p>
- </div>
- </div>
- <footer
- className="modal-foot"
- >
- <SubmitButton
- disabled={false}
- >
- create
- </SubmitButton>
- <ResetButtonLink
- onClick={[Function]}
- >
- cancel
- </ResetButtonLink>
- </footer>
- </form>
-</Modal>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<ul>
- <li
- className="little-spacer-bottom"
- key="foo"
- >
- foo
- </li>
- <li
- className="little-spacer-bottom"
- key="bar"
- >
- bar
- </li>
- <li
- className="little-spacer-bottom"
- >
- <ButtonLink
- className="js-user-more-groups spacer-right"
- onClick={[Function]}
- >
- more_x.2
- </ButtonLink>
- <ButtonIcon
- aria-label="users.update_users_groups.obi"
- className="js-user-groups button-small"
- onClick={[Function]}
- tooltip="users.update_groups"
- >
- <BulletListIcon />
- </ButtonIcon>
- </li>
-</ul>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tr>
- <td
- className="thin text-middle"
- >
- <div
- className="sw-flex sw-items-center"
- >
- <withAppStateContext(Avatar)
- className="sw-shrink-0 sw-mr-4"
- name="One"
- size={36}
- />
- <UserListItemIdentity
- user={
- {
- "active": true,
- "lastConnectionDate": "2019-01-18T15:06:33+0100",
- "local": false,
- "login": "obi",
- "managed": false,
- "name": "One",
- "scmAccounts": [],
- "sonarLintLastConnectionDate": "2019-01-16T15:06:33+0100",
- }
- }
- />
- </div>
- </td>
- <td
- className="thin text-middle"
- >
- <UserScmAccounts
- scmAccounts={[]}
- />
- </td>
- <td
- className="thin nowrap text-middle"
- >
- <DateFromNow
- date="2019-01-18T15:06:33+0100"
- hourPrecision={true}
- />
- </td>
- <td
- className="thin nowrap text-middle"
- >
- <DateFromNow
- date="2019-01-16T15:06:33+0100"
- hourPrecision={true}
- />
- </td>
- <td
- className="thin nowrap text-middle"
- >
- <UserGroups
- groups={[]}
- onUpdateUsers={[MockFunction]}
- user={
- {
- "active": true,
- "lastConnectionDate": "2019-01-18T15:06:33+0100",
- "local": false,
- "login": "obi",
- "managed": false,
- "name": "One",
- "scmAccounts": [],
- "sonarLintLastConnectionDate": "2019-01-16T15:06:33+0100",
- }
- }
- />
- </td>
- <td
- className="thin nowrap text-middle"
- >
- <ButtonIcon
- aria-label="users.update_tokens_for_x.One"
- className="js-user-tokens spacer-left button-small"
- onClick={[Function]}
- tooltip="users.update_tokens"
- >
- <BulletListIcon />
- </ButtonIcon>
- </td>
- <td
- className="thin nowrap text-right text-middle"
- >
- <UserActions
- isCurrentUser={false}
- onUpdateUsers={[MockFunction]}
- user={
- {
- "active": true,
- "lastConnectionDate": "2019-01-18T15:06:33+0100",
- "local": false,
- "login": "obi",
- "managed": false,
- "name": "One",
- "scmAccounts": [],
- "sonarLintLastConnectionDate": "2019-01-16T15:06:33+0100",
- }
- }
- />
- </td>
-</tr>
-`;
-
-exports[`should render correctly without last connection date 1`] = `
-<tr>
- <td
- className="thin text-middle"
- >
- <div
- className="sw-flex sw-items-center"
- >
- <withAppStateContext(Avatar)
- className="sw-shrink-0 sw-mr-4"
- name="One"
- size={36}
- />
- <UserListItemIdentity
- user={
- {
- "active": true,
- "lastConnectionDate": "2019-01-18T15:06:33+0100",
- "local": false,
- "login": "obi",
- "managed": false,
- "name": "One",
- "scmAccounts": [],
- "sonarLintLastConnectionDate": "2019-01-16T15:06:33+0100",
- }
- }
- />
- </div>
- </td>
- <td
- className="thin text-middle"
- >
- <UserScmAccounts
- scmAccounts={[]}
- />
- </td>
- <td
- className="thin nowrap text-middle"
- >
- <DateFromNow
- date="2019-01-18T15:06:33+0100"
- hourPrecision={true}
- />
- </td>
- <td
- className="thin nowrap text-middle"
- >
- <DateFromNow
- date="2019-01-16T15:06:33+0100"
- hourPrecision={true}
- />
- </td>
- <td
- className="thin nowrap text-middle"
- >
- <UserGroups
- groups={[]}
- onUpdateUsers={[MockFunction]}
- user={
- {
- "active": true,
- "lastConnectionDate": "2019-01-18T15:06:33+0100",
- "local": false,
- "login": "obi",
- "managed": false,
- "name": "One",
- "scmAccounts": [],
- "sonarLintLastConnectionDate": "2019-01-16T15:06:33+0100",
- }
- }
- />
- </td>
- <td
- className="thin nowrap text-middle"
- >
- <ButtonIcon
- aria-label="users.update_tokens_for_x.One"
- className="js-user-tokens spacer-left button-small"
- onClick={[Function]}
- tooltip="users.update_tokens"
- >
- <BulletListIcon />
- </ButtonIcon>
- </td>
- <td
- className="thin nowrap text-right text-middle"
- >
- <UserActions
- isCurrentUser={false}
- onUpdateUsers={[MockFunction]}
- user={
- {
- "active": true,
- "lastConnectionDate": "2019-01-18T15:06:33+0100",
- "local": false,
- "login": "obi",
- "managed": false,
- "name": "One",
- "scmAccounts": [],
- "sonarLintLastConnectionDate": "2019-01-16T15:06:33+0100",
- }
- }
- />
- </td>
-</tr>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`#ExternalProvider should render correctly 1`] = `
-<div
- className="js-user-identity-provider little-spacer-top"
->
- <div
- className="identity-provider"
- style={
- {
- "backgroundColor": "blue",
- "color": "#fff",
- }
- }
- >
- <img
- alt="Foo Provider"
- className="little-spacer-right"
- height="14"
- src="icon/path"
- width="14"
- />
- </div>
-</div>
-`;
-
-exports[`#ExternalProvider should render the user external provider and identity 1`] = `
-<div
- className="js-user-identity-provider little-spacer-top"
->
- <span>
- foo
- :
- </span>
-</div>
-`;
-
-exports[`#UserListItemIdentity should render correctly 1`] = `
-<div>
- <div>
- <strong
- className="js-user-name"
- >
- One
- </strong>
- <span
- className="js-user-login note little-spacer-left"
- >
- obi
- </span>
- </div>
- <div
- className="js-user-email little-spacer-top"
- >
- obi.one@empire
- </div>
- <ExternalProvider
- identityProvider={
- {
- "backgroundColor": "blue",
- "iconPath": "icon/path",
- "key": "foo",
- "name": "Foo Provider",
- }
- }
- user={
- {
- "active": true,
- "email": "obi.one@empire",
- "externalProvider": "foo",
- "lastConnectionDate": "2019-01-18T15:06:33+0100",
- "local": false,
- "login": "obi",
- "managed": false,
- "name": "One",
- "scmAccounts": [],
- }
- }
- />
-</div>
-`;
SysInfoCluster,
SysInfoLogging,
SysInfoStandalone,
+ UserGroupMember,
} from '../types/types';
import { CurrentUser, LoggedInUser, User } from '../types/users';
};
}
+export function mockUserGroupMember(overrides: Partial<UserGroupMember> = {}): UserGroupMember {
+ return {
+ login: 'john.doe',
+ name: 'John Doe',
+ managed: false,
+ selected: true,
+ ...overrides,
+ };
+}
+
export function mockDocumentationMarkdown(
overrides: Partial<{ content: string; title: string; key: string }> = {}
): string {