3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 import { act, screen, waitFor } from '@testing-library/react';
22 import userEvent from '@testing-library/user-event';
23 import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
24 import * as React from 'react';
25 import selectEvent from 'react-select-event';
26 import { byLabelText, byRole, byText } from 'testing-library-selector';
27 import PermissionsServiceMock from '../../../../../api/mocks/PermissionsServiceMock';
28 import { mockComponent } from '../../../../../helpers/mocks/component';
29 import { renderApp } from '../../../../../helpers/testReactTestingUtils';
30 import { ComponentQualifier, Visibility } from '../../../../../types/component';
31 import { Permissions } from '../../../../../types/permissions';
32 import { Component } from '../../../../../types/types';
33 import { PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE, PERMISSIONS_ORDER_FOR_VIEW } from '../../../utils';
34 import { PermissionsProjectApp } from '../PermissionsProjectApp';
36 let serviceMock: PermissionsServiceMock;
38 serviceMock = new PermissionsServiceMock();
45 describe('rendering', () => {
47 [ComponentQualifier.Project, 'roles.page.description2', PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE],
48 [ComponentQualifier.Portfolio, 'roles.page.description_portfolio', PERMISSIONS_ORDER_FOR_VIEW],
50 ComponentQualifier.Application,
51 'roles.page.description_application',
52 PERMISSIONS_ORDER_FOR_VIEW,
54 ])('should render correctly for %s', async (qualifier, description, permissions) => {
55 const user = userEvent.setup();
56 const ui = getPageObject(user);
57 renderPermissionsProjectApp({ qualifier, visibility: Visibility.Private });
60 expect(screen.getByText(description)).toBeInTheDocument();
61 permissions.forEach((permission) => {
62 expect(ui.permissionCheckbox('johndoe', permission).get()).toBeInTheDocument();
67 describe('filtering', () => {
68 it('should allow to filter permission holders', async () => {
69 const user = userEvent.setup();
70 const ui = getPageObject(user);
71 renderPermissionsProjectApp();
74 expect(screen.getByText('sonar-users')).toBeInTheDocument();
75 expect(screen.getByText('johndoe')).toBeInTheDocument();
77 await ui.showOnlyUsers();
78 expect(screen.queryByText('sonar-users')).not.toBeInTheDocument();
79 expect(screen.getByText('johndoe')).toBeInTheDocument();
81 await ui.showOnlyGroups();
82 expect(screen.getByText('sonar-users')).toBeInTheDocument();
83 expect(screen.queryByText('johndoe')).not.toBeInTheDocument();
86 expect(screen.getByText('sonar-users')).toBeInTheDocument();
87 expect(screen.getByText('johndoe')).toBeInTheDocument();
89 await ui.searchFor('sonar-adm');
90 expect(screen.getByText('sonar-admins')).toBeInTheDocument();
91 expect(screen.queryByText('sonar-users')).not.toBeInTheDocument();
92 expect(screen.queryByText('johndoe')).not.toBeInTheDocument();
94 await ui.clearSearch();
95 expect(screen.getByText('sonar-users')).toBeInTheDocument();
96 expect(screen.getByText('johndoe')).toBeInTheDocument();
99 it('should allow to show only permission holders with a specific permission', async () => {
100 const user = userEvent.setup();
101 const ui = getPageObject(user);
102 renderPermissionsProjectApp();
103 await ui.appLoaded();
105 expect(screen.getAllByRole('row').length).toBe(7);
106 await ui.toggleFilterByPermission(Permissions.Admin);
107 expect(screen.getAllByRole('row').length).toBe(2);
111 describe('assigning/revoking permissions', () => {
112 it('should allow to apply a permission template', async () => {
113 const user = userEvent.setup();
114 const ui = getPageObject(user);
115 renderPermissionsProjectApp();
116 await ui.appLoaded();
118 await ui.openTemplateModal();
119 expect(ui.confirmApplyTemplateBtn.get()).toBeDisabled();
120 await ui.chooseTemplate('Permission Template 2');
121 expect(ui.templateSuccessfullyApplied.get()).toBeInTheDocument();
122 await ui.closeTemplateModal();
123 expect(ui.templateSuccessfullyApplied.query()).not.toBeInTheDocument();
126 it('should allow to turn a public project private (and vice-versa)', async () => {
127 const user = userEvent.setup();
128 const ui = getPageObject(user);
129 renderPermissionsProjectApp();
130 await ui.appLoaded();
132 expect(ui.visibilityRadio(Visibility.Public).get()).toBeChecked();
134 ui.permissionCheckbox('sonar-users', Permissions.Browse).query()
135 ).not.toBeInTheDocument();
136 await act(async () => {
137 await ui.turnProjectPrivate();
139 expect(ui.visibilityRadio(Visibility.Private).get()).toBeChecked();
140 expect(ui.permissionCheckbox('sonar-users', Permissions.Browse).get()).toBeInTheDocument();
142 await ui.turnProjectPublic();
143 expect(ui.makePublicDisclaimer.get()).toBeInTheDocument();
144 await act(async () => {
145 await ui.confirmTurnProjectPublic();
147 expect(ui.visibilityRadio(Visibility.Public).get()).toBeChecked();
150 it('should add and remove permissions to/from a group', async () => {
151 const user = userEvent.setup();
152 const ui = getPageObject(user);
153 renderPermissionsProjectApp();
154 await ui.appLoaded();
156 expect(ui.permissionCheckbox('sonar-users', Permissions.Admin).get()).not.toBeChecked();
158 await ui.togglePermission('sonar-users', Permissions.Admin);
159 await ui.appLoaded();
160 expect(ui.permissionCheckbox('sonar-users', Permissions.Admin).get()).toBeChecked();
162 await ui.togglePermission('sonar-users', Permissions.Admin);
163 await ui.appLoaded();
164 expect(ui.permissionCheckbox('sonar-users', Permissions.Admin).get()).not.toBeChecked();
167 it('should add and remove permissions to/from a user', async () => {
168 const user = userEvent.setup();
169 const ui = getPageObject(user);
170 renderPermissionsProjectApp();
171 await ui.appLoaded();
173 expect(ui.permissionCheckbox('johndoe', Permissions.Scan).get()).not.toBeChecked();
175 await ui.togglePermission('johndoe', Permissions.Scan);
176 await ui.appLoaded();
177 expect(ui.permissionCheckbox('johndoe', Permissions.Scan).get()).toBeChecked();
179 await ui.togglePermission('johndoe', Permissions.Scan);
180 await ui.appLoaded();
181 expect(ui.permissionCheckbox('johndoe', Permissions.Scan).get()).not.toBeChecked();
185 function getPageObject(user: UserEvent) {
187 loading: byLabelText('loading'),
188 permissionCheckbox: (target: string, permission: Permissions) =>
190 name: `permission.assign_x_to_y.projects_role.${permission}.${target}`,
192 visibilityRadio: (visibility: Visibility) =>
193 byRole('radio', { name: `visibility.${visibility}` }),
194 makePublicDisclaimer: byText(
195 'projects_role.are_you_sure_to_turn_project_to_public.warning.TRK'
197 confirmPublicBtn: byRole('button', { name: 'projects_role.turn_project_to_public.TRK' }),
198 openModalBtn: byRole('button', { name: 'projects_role.apply_template' }),
199 closeModalBtn: byRole('button', { name: 'close' }),
200 templateSelect: byRole('combobox', { name: /template/ }),
201 templateSuccessfullyApplied: byText('projects_role.apply_template.success'),
202 confirmApplyTemplateBtn: byRole('button', { name: 'apply' }),
203 tableHeaderFilter: (permission: Permissions) =>
204 byRole('link', { name: `projects_role.${permission}` }),
205 onlyUsersBtn: byRole('button', { name: 'users.page' }),
206 onlyGroupsBtn: byRole('button', { name: 'user_groups.page' }),
207 showAllBtn: byRole('button', { name: 'all' }),
208 searchInput: byRole('searchbox', { name: 'search.search_for_users_or_groups' }),
214 await waitFor(() => {
215 expect(ui.loading.query()).not.toBeInTheDocument();
218 async togglePermission(target: string, permission: Permissions) {
219 await user.click(ui.permissionCheckbox(target, permission).get());
221 async turnProjectPrivate() {
222 await user.click(ui.visibilityRadio(Visibility.Private).get());
224 async turnProjectPublic() {
225 await user.click(ui.visibilityRadio(Visibility.Public).get());
227 async confirmTurnProjectPublic() {
228 await user.click(ui.confirmPublicBtn.get());
230 async openTemplateModal() {
231 await user.click(ui.openModalBtn.get());
233 async closeTemplateModal() {
234 await user.click(ui.closeModalBtn.get());
236 async chooseTemplate(name: string) {
237 await selectEvent.select(ui.templateSelect.get(), [name]);
238 await user.click(ui.confirmApplyTemplateBtn.get());
240 async toggleFilterByPermission(permission: Permissions) {
241 await user.click(ui.tableHeaderFilter(permission).get());
243 async showOnlyUsers() {
244 await user.click(ui.onlyUsersBtn.get());
246 async showOnlyGroups() {
247 await user.click(ui.onlyGroupsBtn.get());
250 await user.click(ui.showAllBtn.get());
252 async searchFor(name: string) {
253 await user.type(ui.searchInput.get(), name);
255 async clearSearch() {
256 await user.clear(ui.searchInput.get());
261 function renderPermissionsProjectApp(override?: Partial<Component>) {
262 function App({ component }: { component: Component }) {
263 const [realComponent, setRealComponent] = React.useState(component);
265 <PermissionsProjectApp
266 component={realComponent}
267 onComponentChange={(changes: Partial<Component>) => {
268 setRealComponent({ ...realComponent, ...changes });
277 component={mockComponent({
278 visibility: Visibility.Public,
280 canUpdateProjectVisibilityToPrivate: true,
281 canApplyPermissionTemplate: true,