From: stanislavh Date: Thu, 30 Mar 2023 09:02:59 +0000 (+0200) Subject: SONAR-18931 Migrate to RTL for PRDecorationBinding X-Git-Tag: 10.0.0.68432~12 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=d68d70ceca6263a649d597eb21f6d5a0ad873f94;p=sonarqube.git SONAR-18931 Migrate to RTL for PRDecorationBinding --- diff --git a/server/sonar-web/__mocks__/react-intl.tsx b/server/sonar-web/__mocks__/react-intl.tsx index b2ab27be3fb..9e8c8170dd1 100644 --- a/server/sonar-web/__mocks__/react-intl.tsx +++ b/server/sonar-web/__mocks__/react-intl.tsx @@ -30,5 +30,5 @@ module.exports = { ))} ); - } + }, }; diff --git a/server/sonar-web/src/main/js/api/mocks/AlmSettingsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/AlmSettingsServiceMock.ts index 4b15331071a..864aac3a0f5 100644 --- a/server/sonar-web/src/main/js/api/mocks/AlmSettingsServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/AlmSettingsServiceMock.ts @@ -24,10 +24,18 @@ import { AlmSettingsBindingDefinitions, AlmSettingsInstance, AzureBindingDefinition, + AzureProjectAlmBindingParams, BitbucketCloudBindingDefinition, + BitbucketCloudProjectAlmBindingParams, + BitbucketProjectAlmBindingParams, BitbucketServerBindingDefinition, GithubBindingDefinition, + GithubProjectAlmBindingParams, GitlabBindingDefinition, + GitlabProjectAlmBindingParams, + ProjectAlmBindingConfigurationErrors, + ProjectAlmBindingParams, + ProjectAlmBindingResponse, } from '../../types/alm-settings'; import { countBoundProjects, @@ -37,14 +45,22 @@ import { createGithubConfiguration, createGitlabConfiguration, deleteConfiguration, + deleteProjectAlmBinding, getAlmDefinitions, getAlmSettings, + getProjectAlmBinding, + setProjectAzureBinding, + setProjectBitbucketBinding, + setProjectBitbucketCloudBinding, + setProjectGithubBinding, + setProjectGitlabBinding, updateAzureConfiguration, updateBitbucketCloudConfiguration, updateBitbucketServerConfiguration, updateGithubConfiguration, updateGitlabConfiguration, validateAlmSettings, + validateProjectAlmBinding, } from '../alm-settings'; const defaultAlmDefinitions = { @@ -85,10 +101,20 @@ const defaultAlmSettings = [ }), ]; +interface EnhancedProjectAlmBindingParam extends ProjectAlmBindingParams { + projectName?: string; + repositoryName?: string; + repository?: string; + slug?: string; + summaryCommentEnabled?: boolean; +} + export default class AlmSettingsServiceMock { #almDefinitions: AlmSettingsBindingDefinitions; #almSettings: AlmSettingsInstance[]; #definitionError = ''; + #projectsBindings: { [key: string]: ProjectAlmBindingResponse | undefined } = {}; + #projectBindingConfigurationErrors: ProjectAlmBindingConfigurationErrors | undefined = undefined; constructor() { this.#almSettings = cloneDeep(defaultAlmSettings); @@ -116,6 +142,18 @@ export default class AlmSettingsServiceMock { jest .mocked(updateBitbucketCloudConfiguration) .mockImplementation(this.handleUpdateBitbucketCloudConfiguration); + jest.mocked(getProjectAlmBinding).mockImplementation(this.handleGetProjectBinding); + jest.mocked(deleteProjectAlmBinding).mockImplementation(this.handleDeleteProjectAlmBinding); + jest.mocked(setProjectAzureBinding).mockImplementation(this.handleSetProjectAzureBinding); + jest + .mocked(setProjectBitbucketBinding) + .mockImplementation(this.handleSetProjectBitbucketBinding); + jest + .mocked(setProjectBitbucketCloudBinding) + .mockImplementation(this.handleSetProjectBitbucketCloudBinding); + jest.mocked(setProjectGithubBinding).mockImplementation(this.handleSetProjectGithubBinding); + jest.mocked(setProjectGitlabBinding).mockImplementation(this.handleSetProjectGitlabBinding); + jest.mocked(validateProjectAlmBinding).mockImplementation(this.handleValidateProjectAlmBinding); } handleGetAlmDefinitions = () => { @@ -234,9 +272,71 @@ export default class AlmSettingsServiceMock { return this.reply(undefined); }; + handleGetProjectBinding = (project: string) => { + const projectBinding = this.#projectsBindings[project]; + + if (projectBinding === undefined) { + return Promise.reject( + new Response('', { + status: 404, + }) + ); + } + + return this.reply(projectBinding); + }; + + handleSetProjectBinding = (alm: AlmKeys, data: EnhancedProjectAlmBindingParam) => { + this.#projectsBindings[data.project] = { + alm, + key: data.almSetting, + repository: data.repositoryName ?? (data.repository as string), + monorepo: data.monorepo, + slug: data.projectName ?? data.slug, + summaryCommentEnabled: data.summaryCommentEnabled ?? false, + url: 'https://company.com/project', + }; + return this.reply(undefined); + }; + + handleSetProjectAzureBinding = (data: AzureProjectAlmBindingParams) => { + return this.handleSetProjectBinding(AlmKeys.Azure, data); + }; + + handleSetProjectBitbucketBinding = (data: BitbucketProjectAlmBindingParams) => { + return this.handleSetProjectBinding(AlmKeys.BitbucketServer, data); + }; + + handleSetProjectBitbucketCloudBinding = (data: BitbucketCloudProjectAlmBindingParams) => { + return this.handleSetProjectBinding(AlmKeys.BitbucketCloud, data); + }; + + handleSetProjectGithubBinding = (data: GithubProjectAlmBindingParams) => { + return this.handleSetProjectBinding(AlmKeys.GitHub, data); + }; + + handleSetProjectGitlabBinding = (data: GitlabProjectAlmBindingParams) => { + return this.handleSetProjectBinding(AlmKeys.GitLab, data); + }; + + handleValidateProjectAlmBinding = () => { + return this.reply(this.#projectBindingConfigurationErrors); + }; + + setProjectBindingConfigurationErrors = (errors?: ProjectAlmBindingConfigurationErrors) => { + this.#projectBindingConfigurationErrors = errors; + }; + + handleDeleteProjectAlmBinding = (project: string) => { + this.#projectsBindings[project] = undefined; + return this.reply(undefined); + }; + reset = () => { this.#almSettings = cloneDeep(defaultAlmSettings); this.#almDefinitions = cloneDeep(defaultAlmDefinitions); + this.#projectsBindings = {}; + this.#projectBindingConfigurationErrors = undefined; this.setDefinitionErrorMessage(''); }; diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Header-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Header-test.tsx index 04286b17cd1..4dbfe4e9a12 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Header-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Header-test.tsx @@ -20,6 +20,7 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; +import { mockProjectAlmBindingResponse } from '../../../../../helpers/mocks/alm-settings'; import { mockMainBranch, mockPullRequest, @@ -145,7 +146,11 @@ it('should show the correct help tooltip when branch support is not enabled', () renderHeader( { currentUser: mockLoggedInUser(), - projectBinding: { alm: AlmKeys.GitLab, key: 'key', monorepo: true }, + projectBinding: mockProjectAlmBindingResponse({ + alm: AlmKeys.GitLab, + key: 'key', + monorepo: true, + }), }, [] ); diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-it.tsx index 4c5391703e3..1cb65f52ee6 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-it.tsx @@ -40,41 +40,15 @@ afterEach(() => { almSettings.reset(); }); -const ui = { - almHeading: byRole('heading', { name: 'settings.almintegration.title' }), - emptyIntro: (almKey: AlmKeys) => byText(`settings.almintegration.empty.${almKey}`), - createConfigurationButton: byRole('button', { name: 'settings.almintegration.create' }), - tab: (almKey: AlmKeys) => - byRole('tab', { name: `${almKey} settings.almintegration.tab.${almKey}` }), - bitbucketConfiguration: (almKey: AlmKeys.BitbucketCloud | AlmKeys.BitbucketServer) => - byRole('button', { name: `alm.${almKey}.long` }), - configurationInput: (id: string) => - byRole('textbox', { name: `settings.almintegration.form.${id}` }), - updateSecretValueButton: (key: string) => - byRole('button', { - name: `settings.almintegration.form.secret.update_field_x.settings.almintegration.form.${key}`, - }), - saveConfigurationButton: byRole('button', { name: 'settings.almintegration.form.save' }), - editConfigurationButton: (key: string) => - byRole('button', { name: `settings.almintegration.edit_configuration.${key}` }), - deleteConfigurationButton: (key: string) => - byRole('button', { name: `settings.almintegration.delete_configuration.${key}` }), - cancelButton: byRole('button', { name: 'cancel' }), - confirmDelete: byRole('button', { name: 'delete' }), - checkConfigurationButton: (key: string) => - byRole('button', { name: `settings.almintegration.check_configuration_x.${key}` }), - validationErrorMessage: byRole('alert'), - validationSuccessMessage: byRole('status'), -}; - describe('github tab', () => { it('can create/edit/delete new configuration', async () => { + const { ui } = getPageObjects(); const { rerender } = renderAlmIntegration(); expect(await ui.almHeading.find()).toBeInTheDocument(); expect(ui.emptyIntro(AlmKeys.GitHub).get()).toBeInTheDocument(); // Create new configuration - await createConfiguration('Name', { + await ui.createConfiguration('Name', { 'name.github': 'Name', 'url.github': 'https://api.github.com', app_id: 'Github App ID', @@ -83,14 +57,14 @@ describe('github tab', () => { private_key: 'Key', }); - await editConfiguration('New Name', 'Name', 'client_secret.github', AlmKeys.GitHub); + await ui.editConfiguration('New Name', 'Name', 'client_secret.github', AlmKeys.GitHub); - await checkConfiguration('New Name'); + await ui.checkConfiguration('New Name'); rerender(); expect(await screen.findByRole('heading', { name: 'New Name' })).toBeInTheDocument(); - await deleteConfiguration('New Name'); + await ui.deleteConfiguration('New Name'); expect(ui.emptyIntro(AlmKeys.GitHub).get()).toBeInTheDocument(); }); }); @@ -99,6 +73,8 @@ describe.each([AlmKeys.GitLab, AlmKeys.Azure])( '%s tab', (almKey: AlmKeys.Azure | AlmKeys.GitLab) => { it('can create/edit/delete new configuration', async () => { + const { ui } = getPageObjects(); + renderAlmIntegration(); expect(await ui.almHeading.find()).toBeInTheDocument(); @@ -106,7 +82,7 @@ describe.each([AlmKeys.GitLab, AlmKeys.Azure])( expect(ui.emptyIntro(almKey).get()).toBeInTheDocument(); // Create new configuration - await createConfiguration('Name', { + await ui.createConfiguration('Name', { [`name.${almKey}`]: 'Name', [`url.${almKey}`]: 'https://api.alm.com', personal_access_token: 'Access Token', @@ -115,11 +91,11 @@ describe.each([AlmKeys.GitLab, AlmKeys.Azure])( // Cannot create another configuration without Multiple Alm feature expect(ui.createConfigurationButton.get()).toBeDisabled(); - await editConfiguration('New Name', 'Name', 'personal_access_token', almKey); + await ui.editConfiguration('New Name', 'Name', 'personal_access_token', almKey); - await checkConfiguration('New Name'); + await ui.checkConfiguration('New Name'); - await deleteConfiguration('New Name'); + await ui.deleteConfiguration('New Name'); expect(ui.emptyIntro(almKey).get()).toBeInTheDocument(); }); } @@ -127,6 +103,7 @@ describe.each([AlmKeys.GitLab, AlmKeys.Azure])( describe('bitbucket tab', () => { it('can create/edit/delete new configuration', async () => { + const { ui } = getPageObjects(); renderAlmIntegration([Feature.MultipleAlm]); expect(await ui.almHeading.find()).toBeInTheDocument(); @@ -134,7 +111,7 @@ describe('bitbucket tab', () => { expect(ui.emptyIntro(AlmKeys.BitbucketServer).get()).toBeInTheDocument(); // Create new Bitbucket Server configuration - await createConfiguration( + await ui.createConfiguration( 'Name', { 'name.bitbucket': 'Name', @@ -145,7 +122,7 @@ describe('bitbucket tab', () => { ); // Create new Bitbucket Cloud configuration - await createConfiguration( + await ui.createConfiguration( 'Name Cloud', { 'name.bitbucket': 'Name Cloud', @@ -157,77 +134,124 @@ describe('bitbucket tab', () => { ); // Edit, check delete Bitbucket Server configuration - await editConfiguration('New Name', 'Name', 'personal_access_token', AlmKeys.BitbucketServer); + await ui.editConfiguration( + 'New Name', + 'Name', + 'personal_access_token', + AlmKeys.BitbucketServer + ); - await checkConfiguration('New Name'); + await ui.checkConfiguration('New Name'); - await deleteConfiguration('New Name'); + await ui.deleteConfiguration('New Name'); // Cloud configuration still exists expect(screen.getByRole('heading', { name: 'Name Cloud' })).toBeInTheDocument(); }); }); -async function createConfiguration( - name: string, - params: { [key: string]: string }, - almKey?: AlmKeys.BitbucketCloud | AlmKeys.BitbucketServer -) { - await userEvent.click(ui.createConfigurationButton.get()); - expect(ui.saveConfigurationButton.get()).toBeDisabled(); - - if (almKey) { - await userEvent.click(ui.bitbucketConfiguration(almKey).get()); +function getPageObjects() { + const user = userEvent.setup(); + + const ui = { + almHeading: byRole('heading', { name: 'settings.almintegration.title' }), + emptyIntro: (almKey: AlmKeys) => byText(`settings.almintegration.empty.${almKey}`), + createConfigurationButton: byRole('button', { name: 'settings.almintegration.create' }), + tab: (almKey: AlmKeys) => + byRole('tab', { name: `${almKey} settings.almintegration.tab.${almKey}` }), + bitbucketConfiguration: (almKey: AlmKeys.BitbucketCloud | AlmKeys.BitbucketServer) => + byRole('button', { name: `alm.${almKey}.long` }), + configurationInput: (id: string) => + byRole('textbox', { name: `settings.almintegration.form.${id}` }), + updateSecretValueButton: (key: string) => + byRole('button', { + name: `settings.almintegration.form.secret.update_field_x.settings.almintegration.form.${key}`, + }), + saveConfigurationButton: byRole('button', { name: 'settings.almintegration.form.save' }), + editConfigurationButton: (key: string) => + byRole('button', { name: `settings.almintegration.edit_configuration.${key}` }), + deleteConfigurationButton: (key: string) => + byRole('button', { name: `settings.almintegration.delete_configuration.${key}` }), + cancelButton: byRole('button', { name: 'cancel' }), + confirmDelete: byRole('button', { name: 'delete' }), + checkConfigurationButton: (key: string) => + byRole('button', { name: `settings.almintegration.check_configuration_x.${key}` }), + validationErrorMessage: byRole('alert'), + validationSuccessMessage: byRole('status'), + }; + + async function createConfiguration( + name: string, + params: { [key: string]: string }, + almKey?: AlmKeys.BitbucketCloud | AlmKeys.BitbucketServer + ) { + await userEvent.click(ui.createConfigurationButton.get()); + expect(ui.saveConfigurationButton.get()).toBeDisabled(); + + if (almKey) { + await userEvent.click(ui.bitbucketConfiguration(almKey).get()); + } + + for (const [key, value] of Object.entries(params)) { + // eslint-disable-next-line no-await-in-loop + await userEvent.type(ui.configurationInput(key).get(), value); + } + expect(ui.saveConfigurationButton.get()).toBeEnabled(); + await userEvent.click(ui.saveConfigurationButton.get()); + + // New configuration is created + expect(screen.getByRole('heading', { name })).toBeInTheDocument(); } - for (const [key, value] of Object.entries(params)) { - // eslint-disable-next-line no-await-in-loop - await userEvent.type(ui.configurationInput(key).get(), value); + async function editConfiguration( + newName: string, + currentName: string, + secretId: string, + almKey: AlmKeys + ) { + almSettings.setDefinitionErrorMessage('Something is wrong'); + await userEvent.click(ui.editConfigurationButton(currentName).get()); + expect(ui.configurationInput(secretId).query()).not.toBeInTheDocument(); + await userEvent.click(ui.updateSecretValueButton(secretId).get()); + await userEvent.type(ui.configurationInput(secretId).get(), 'New Secret Value'); + await userEvent.clear(ui.configurationInput(`name.${almKey}`).get()); + await userEvent.type(ui.configurationInput(`name.${almKey}`).get(), newName); + await userEvent.click(ui.saveConfigurationButton.get()); + + // Existing configuration is edited + expect(screen.queryByRole('heading', { name: currentName })).not.toBeInTheDocument(); + expect(screen.getByRole('heading', { name: newName })).toBeInTheDocument(); + expect(ui.validationErrorMessage.get()).toHaveTextContent('Something is wrong'); } - expect(ui.saveConfigurationButton.get()).toBeEnabled(); - await userEvent.click(ui.saveConfigurationButton.get()); - - // New configuration is created - expect(screen.getByRole('heading', { name })).toBeInTheDocument(); -} -async function editConfiguration( - newName: string, - currentName: string, - secretId: string, - almKey: AlmKeys -) { - almSettings.setDefinitionErrorMessage('Something is wrong'); - await userEvent.click(ui.editConfigurationButton(currentName).get()); - expect(ui.configurationInput(secretId).query()).not.toBeInTheDocument(); - await userEvent.click(ui.updateSecretValueButton(secretId).get()); - await userEvent.type(ui.configurationInput(secretId).get(), 'New Secret Value'); - await userEvent.clear(ui.configurationInput(`name.${almKey}`).get()); - await userEvent.type(ui.configurationInput(`name.${almKey}`).get(), newName); - await userEvent.click(ui.saveConfigurationButton.get()); - - // Existing configuration is edited - expect(screen.queryByRole('heading', { name: currentName })).not.toBeInTheDocument(); - expect(screen.getByRole('heading', { name: newName })).toBeInTheDocument(); - expect(ui.validationErrorMessage.get()).toHaveTextContent('Something is wrong'); -} + async function checkConfiguration(name: string) { + almSettings.setDefinitionErrorMessage(''); + await userEvent.click(ui.checkConfigurationButton(name).get()); + expect(ui.validationSuccessMessage.getAll()[0]).toHaveTextContent( + 'alert.tooltip.successsettings.almintegration.configuration_valid' + ); + } -async function checkConfiguration(name: string) { - almSettings.setDefinitionErrorMessage(''); - await userEvent.click(ui.checkConfigurationButton(name).get()); - expect(ui.validationSuccessMessage.getAll()[0]).toHaveTextContent( - 'alert.tooltip.successsettings.almintegration.configuration_valid' - ); -} + async function deleteConfiguration(name: string) { + await userEvent.click(ui.deleteConfigurationButton(name).get()); + await userEvent.click(ui.cancelButton.get()); + expect(screen.getByRole('heading', { name })).toBeInTheDocument(); -async function deleteConfiguration(name: string) { - await userEvent.click(ui.deleteConfigurationButton(name).get()); - await userEvent.click(ui.cancelButton.get()); - expect(screen.getByRole('heading', { name })).toBeInTheDocument(); + await userEvent.click(ui.deleteConfigurationButton(name).get()); + await userEvent.click(ui.confirmDelete.get()); + expect(screen.queryByRole('heading', { name })).not.toBeInTheDocument(); + } - await userEvent.click(ui.deleteConfigurationButton(name).get()); - await userEvent.click(ui.confirmDelete.get()); - expect(screen.queryByRole('heading', { name })).not.toBeInTheDocument(); + return { + ui: { + ...ui, + createConfiguration, + editConfiguration, + deleteConfiguration, + checkConfiguration, + }, + user, + }; } function renderAlmIntegration(features: Feature[] = []) { diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx index 9a833ae0e01..0c02ad50e6b 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx @@ -73,7 +73,7 @@ function renderFieldWrapper( ); } -function renderHelp({ help, helpExample, helpParams, id }: CommonFieldProps) { +function renderHelp({ help, helpExample, helpParams = {}, id }: CommonFieldProps) { return ( help && ( <> @@ -113,7 +113,7 @@ function renderBooleanField( renderLabel({ ...props, optional: true }),
- onFieldChange(propKey, v)} value={value} /> + onFieldChange(propKey, v)} value={value} /> {value == null && {translate('settings.not_set')}}
{inputExtra} diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx index cdf09eecf4c..aaf2ec9d983 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { cloneDeep } from 'lodash'; import * as React from 'react'; import { deleteProjectAlmBinding, @@ -75,10 +76,12 @@ const REQUIRED_FIELDS_BY_ALM: { [AlmKeys.GitLab]: ['repository'], }; +const INITIAL_FORM_DATA = { key: '', repository: '', monorepo: false }; + export class PRDecorationBinding extends React.PureComponent { mounted = false; state: State = { - formData: { key: '', monorepo: false }, + formData: cloneDeep(INITIAL_FORM_DATA), instances: [], isChanged: false, isConfigured: false, @@ -169,23 +172,14 @@ export class PRDecorationBinding extends React.PureComponent { submitProjectAlmBinding( alm: AlmKeys, key: string, - almSpecificFields?: Omit + almSpecificFields: Omit ): Promise { const almSetting = key; + const { repository, slug = '', monorepo = false } = almSpecificFields; const project = this.props.component.key; - const repository = almSpecificFields?.repository; - const slug = almSpecificFields?.slug; - const monorepo = almSpecificFields?.monorepo ?? false; - - if (!repository) { - return Promise.reject(); - } switch (alm) { case AlmKeys.Azure: { - if (!slug) { - return Promise.reject(); - } return setProjectAzureBinding({ almSetting, project, @@ -195,9 +189,6 @@ export class PRDecorationBinding extends React.PureComponent { }); } case AlmKeys.BitbucketServer: { - if (!slug) { - return Promise.reject(); - } return setProjectBitbucketBinding({ almSetting, project, @@ -314,7 +305,7 @@ export class PRDecorationBinding extends React.PureComponent { return { formData: newFormData, isValid: this.validateForm(newFormData), - isChanged: !this.isDataSame(newFormData, originalData || { key: '', monorepo: false }), + isChanged: !this.isDataSame(newFormData, originalData || cloneDeep(INITIAL_FORM_DATA)), successfullyUpdated: false, }; }); diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx deleted file mode 100644 index ef972b6d427..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockAlmSettingsInstance } from '../../../../../helpers/mocks/alm-settings'; -import { AlmKeys, AlmSettingsInstance } from '../../../../../types/alm-settings'; -import { AlmSpecificForm, AlmSpecificFormProps } from '../AlmSpecificForm'; - -it.each([ - [AlmKeys.Azure], - [AlmKeys.BitbucketServer], - [AlmKeys.BitbucketCloud], - [AlmKeys.GitHub], - [AlmKeys.GitLab], -])('should render correctly for %s', (alm) => { - expect(shallowRender(alm)).toMatchSnapshot(); -}); - -it.each([ - [ - AlmKeys.BitbucketServer, - [mockAlmSettingsInstance({ alm: AlmKeys.BitbucketServer, url: 'http://bbs.example.com' })], - ], - [AlmKeys.GitHub, [mockAlmSettingsInstance({ url: 'http://example.com/api/v3' })]], - [AlmKeys.GitHub, [mockAlmSettingsInstance({ url: 'http://api.github.com' })]], -])( - 'should render correctly for %s if an instance URL is provided', - (alm: AlmKeys, instances: AlmSettingsInstance[]) => { - expect(shallowRender(alm, { instances })).toMatchSnapshot(); - } -); - -it('should render the monorepo field when the feature is supported', () => { - expect(shallowRender(AlmKeys.Azure, { hasFeature: jest.fn(() => true) })).toMatchSnapshot(); -}); - -function shallowRender(alm: AlmKeys, props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-it.tsx new file mode 100644 index 00000000000..6120dab20e1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-it.tsx @@ -0,0 +1,207 @@ +/* + * 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 userEvent from '@testing-library/user-event'; +import React from 'react'; +import selectEvent from 'react-select-event'; +import { byRole } from 'testing-library-selector'; +import AlmSettingsServiceMock from '../../../../../api/mocks/AlmSettingsServiceMock'; +import CurrentUserContextProvider from '../../../../../app/components/current-user/CurrentUserContextProvider'; +import { mockComponent } from '../../../../../helpers/mocks/component'; +import { mockCurrentUser } from '../../../../../helpers/testMocks'; +import { renderComponent } from '../../../../../helpers/testReactTestingUtils'; +import { + AlmKeys, + ProjectAlmBindingConfigurationErrorScope, +} from '../../../../../types/alm-settings'; +import { Component } from '../../../../../types/types'; +import { CurrentUser } from '../../../../../types/users'; +import PRDecorationBinding from '../PRDecorationBinding'; + +jest.mock('../../../../../api/alm-settings'); + +let almSettings: AlmSettingsServiceMock; + +beforeAll(() => { + almSettings = new AlmSettingsServiceMock(); +}); + +afterEach(() => { + almSettings.reset(); +}); + +const inputsList = { + [AlmKeys.GitLab]: { 'gitlab.repository': 'Repository', monorepo: false }, + [AlmKeys.GitHub]: { + 'github.repository': 'Repository', + 'github.summary_comment_setting': false, + monorepo: false, + }, + [AlmKeys.Azure]: { + 'azure.repository': 'Repository', + 'azure.project': 'Project', + monorepo: false, + }, + [AlmKeys.BitbucketCloud]: { 'bitbucketcloud.repository': 'Repository', monorepo: false }, + [AlmKeys.BitbucketServer]: { + 'bitbucket.repository': 'Repository', + 'bitbucket.slug': 'Slug', + monorepo: false, + }, +}; + +it.each([ + { + key: 'conf-final-1', + alm: AlmKeys.GitLab, + }, + { + key: 'conf-github-1', + alm: AlmKeys.GitHub, + }, + { + key: 'conf-azure-1', + alm: AlmKeys.Azure, + }, + { + key: 'conf-bitbucketcloud-1', + alm: AlmKeys.BitbucketCloud, + }, + { + key: 'conf-bitbucketserver-1', + alm: AlmKeys.BitbucketServer, + }, +])( + 'should get, set, delete and validate binding for $alm', + async ({ key, alm }: { key: string; alm: AlmKeys }) => { + const { ui, user } = getPageObjects(); + almSettings.setProjectBindingConfigurationErrors({ + scope: ProjectAlmBindingConfigurationErrorScope.Global, + errors: [{ msg: 'cute error' }], + }); + const { rerender } = renderPRDecorationBinding(); + expect(await ui.mainTitle.find()).toBeInTheDocument(); + + // Set form data + await selectEvent.select(ui.input('name', 'combobox').get(), (content) => + content.includes(key) + ); + + const list = inputsList[alm]; + for (const [inputId, value] of Object.entries(list)) { + // eslint-disable-next-line no-await-in-loop + await ui.setInput(inputId, value); + } + // Save form and check for errors + await user.click(ui.saveButton.get()); + expect(ui.validationErrorMsg.get()).toHaveTextContent('cute error'); + + // Check validation with errors + await user.click(ui.validateButton.get()); + expect(ui.validationErrorMsg.get()).toHaveTextContent('cute error'); + + // Save form and check for errors + almSettings.setProjectBindingConfigurationErrors(undefined); + await ui.setInput( + Object.keys(list).find((key) => key.endsWith('.repository')) as string, + 'Anything' + ); + await user.click(ui.saveButton.get()); + expect(await ui.validationSuccessMsg.find()).toHaveTextContent( + 'settings.pr_decoration.binding.check_configuration.success' + ); + + await user.click(ui.validateButton.get()); + expect(ui.validationSuccessMsg.get()).toHaveTextContent( + 'settings.pr_decoration.binding.check_configuration.success' + ); + + // Rerender and verify that validation is done for binding + rerender( + + ); + expect(await ui.validationSuccessMsg.find()).toHaveTextContent( + 'settings.pr_decoration.binding.check_configuration.success' + ); + expect(ui.saveButton.query()).not.toBeInTheDocument(); + + // Reset binding + await user.click(ui.resetButton.get()); + expect(ui.input('', 'textbox').query()).not.toBeInTheDocument(); + expect(ui.input('', 'switch').query()).not.toBeInTheDocument(); + } +); + +function getPageObjects() { + const user = userEvent.setup(); + + async function setInput(inputId: string, value: string | boolean) { + if (typeof value === 'boolean') { + if (value) { + await user.click(ui.input(inputId, 'switch').get()); + } + } else { + const input = ui.input(inputId, 'textbox').get(); + await user.clear(input); + await user.type(input, value); + } + } + + const ui = { + mainTitle: byRole('heading', { name: 'settings.pr_decoration.binding.title' }), + input: (id: string, role: 'combobox' | 'switch' | 'textbox') => + byRole(role, { name: new RegExp(`settings.pr_decoration.binding.form.${id}`) }), + saveButton: byRole('button', { name: 'save' }), + resetButton: byRole('button', { name: 'reset_verb' }), + validateButton: byRole('button', { + name: 'settings.pr_decoration.binding.check_configuration', + }), + validationErrorMsg: byRole('alert'), + validationSuccessMsg: byRole('status'), + setInput, + }; + + return { + ui, + user, + }; +} + +function MockedPRDecorationBinding({ + component, + currentUser, +}: { + component: Component; + currentUser: CurrentUser; +}) { + return ( + + + + ); +} + +function renderPRDecorationBinding( + component: Component = mockComponent(), + currentUser: CurrentUser = mockCurrentUser() +) { + return renderComponent( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx deleted file mode 100644 index ec17a27cbba..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx +++ /dev/null @@ -1,423 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { - deleteProjectAlmBinding, - getAlmSettings, - getProjectAlmBinding, - setProjectAzureBinding, - setProjectBitbucketBinding, - setProjectBitbucketCloudBinding, - setProjectGithubBinding, - setProjectGitlabBinding, - validateProjectAlmBinding, -} from '../../../../../api/alm-settings'; -import { - mockAlmSettingsInstance, - mockProjectAlmBindingConfigurationErrors, - mockProjectAlmBindingResponse, -} from '../../../../../helpers/mocks/alm-settings'; -import { mockComponent } from '../../../../../helpers/mocks/component'; -import { mockCurrentUser } from '../../../../../helpers/testMocks'; -import { waitAndUpdate } from '../../../../../helpers/testUtils'; -import { AlmKeys, AlmSettingsInstance } from '../../../../../types/alm-settings'; -import { PRDecorationBinding } from '../PRDecorationBinding'; -import PRDecorationBindingRenderer from '../PRDecorationBindingRenderer'; - -jest.mock('../../../../../api/alm-settings', () => ({ - getAlmSettings: jest.fn().mockResolvedValue([]), - getProjectAlmBinding: jest.fn().mockResolvedValue(undefined), - setProjectAzureBinding: jest.fn().mockResolvedValue(undefined), - setProjectBitbucketBinding: jest.fn().mockResolvedValue(undefined), - setProjectGithubBinding: jest.fn().mockResolvedValue(undefined), - setProjectGitlabBinding: jest.fn().mockResolvedValue(undefined), - setProjectBitbucketCloudBinding: jest.fn().mockResolvedValue(undefined), - deleteProjectAlmBinding: jest.fn().mockResolvedValue(undefined), - validateProjectAlmBinding: jest.fn().mockResolvedValue(undefined), -})); - -const PROJECT_KEY = 'project-key'; - -beforeEach(() => { - jest.clearAllMocks(); -}); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should fill selects and fill formdata', async () => { - const url = 'github.com'; - const instances = [{ key: 'instance1', url, alm: AlmKeys.GitHub }]; - const formdata = { - key: 'instance1', - repository: 'account/repo', - }; - (getAlmSettings as jest.Mock).mockResolvedValueOnce(instances); - (getProjectAlmBinding as jest.Mock).mockResolvedValueOnce(formdata); - - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - - expect(wrapper.state().loading).toBe(false); - expect(wrapper.state().formData).toEqual(formdata); - expect(wrapper.state().isChanged).toBe(false); -}); - -it('should handle reset', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.setState({ - formData: { - key: 'whatever', - repository: 'something/else', - monorepo: false, - }, - }); - - wrapper.instance().handleReset(); - await waitAndUpdate(wrapper); - - expect(deleteProjectAlmBinding).toHaveBeenCalledWith(PROJECT_KEY); - expect(wrapper.state().formData).toEqual({ key: '', repository: '', slug: '', monorepo: false }); - expect(wrapper.state().isChanged).toBe(false); -}); - -describe('handleSubmit', () => { - const instances: AlmSettingsInstance[] = [ - { key: 'github', alm: AlmKeys.GitHub }, - { key: 'azure', alm: AlmKeys.Azure }, - { key: 'bitbucket', alm: AlmKeys.BitbucketServer }, - { key: 'gitlab', alm: AlmKeys.GitLab }, - { key: 'bitbucketcloud', alm: AlmKeys.BitbucketCloud }, - ]; - - it('should work for github', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - const githubKey = 'github'; - const repository = 'repo/path'; - const summaryCommentEnabled = true; - const monorepo = true; - wrapper.setState({ - formData: { key: githubKey, repository, summaryCommentEnabled, monorepo }, - instances, - }); - wrapper.instance().handleSubmit(); - await waitAndUpdate(wrapper); - - expect(setProjectGithubBinding).toHaveBeenCalledWith({ - almSetting: githubKey, - project: PROJECT_KEY, - repository, - summaryCommentEnabled, - monorepo, - }); - expect(wrapper.state().successfullyUpdated).toBe(true); - }); - - it('should work for azure', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - const azureKey = 'azure'; - const repository = 'az-rep'; - const slug = 'az-project'; - const monorepo = true; - wrapper.setState({ - formData: { key: azureKey, repository, slug, monorepo }, - instances, - }); - wrapper.instance().handleSubmit(); - await waitAndUpdate(wrapper); - - expect(setProjectAzureBinding).toHaveBeenCalledWith({ - almSetting: azureKey, - project: PROJECT_KEY, - projectName: slug, - repositoryName: repository, - monorepo, - }); - expect(wrapper.state().successfullyUpdated).toBe(true); - }); - - it('should work for bitbucket', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - const bitbucketKey = 'bitbucket'; - const repository = 'repoKey'; - const slug = 'repoSlug'; - const monorepo = true; - wrapper.setState({ formData: { key: bitbucketKey, repository, slug, monorepo }, instances }); - wrapper.instance().handleSubmit(); - await waitAndUpdate(wrapper); - - expect(setProjectBitbucketBinding).toHaveBeenCalledWith({ - almSetting: bitbucketKey, - project: PROJECT_KEY, - repository, - slug, - monorepo, - }); - expect(wrapper.state().successfullyUpdated).toBe(true); - }); - - it('should work for gitlab', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - const gitlabKey = 'gitlab'; - const repository = 'repo'; - const monorepo = true; - wrapper.setState({ - formData: { key: gitlabKey, repository, monorepo }, - instances, - }); - wrapper.instance().handleSubmit(); - await waitAndUpdate(wrapper); - - expect(setProjectGitlabBinding).toHaveBeenCalledWith({ - almSetting: gitlabKey, - project: PROJECT_KEY, - repository, - monorepo, - }); - expect(wrapper.state().successfullyUpdated).toBe(true); - }); - - it('should work for bitbucket cloud', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - const bitbucketKey = 'bitbucketcloud'; - const repository = 'repoKey'; - const monorepo = true; - wrapper.setState({ formData: { key: bitbucketKey, repository, monorepo }, instances: [] }); - wrapper.instance().handleSubmit(); - await waitAndUpdate(wrapper); - expect(setProjectBitbucketCloudBinding).not.toHaveBeenCalled(); - - wrapper.setState({ formData: { key: bitbucketKey, repository, monorepo }, instances }); - wrapper.instance().handleSubmit(); - await waitAndUpdate(wrapper); - - expect(setProjectBitbucketCloudBinding).toHaveBeenCalledWith({ - almSetting: bitbucketKey, - project: PROJECT_KEY, - repository, - monorepo, - }); - expect(wrapper.state().successfullyUpdated).toBe(true); - }); -}); - -describe.each([[500], [404]])('For status %i', (status) => { - it('should handle failures gracefully', async () => { - const newFormData = { - key: 'whatever', - repository: 'something/else', - monorepo: false, - }; - - (getProjectAlmBinding as jest.Mock).mockRejectedValueOnce({ status }); - (setProjectGithubBinding as jest.Mock).mockRejectedValueOnce({ status }); - (deleteProjectAlmBinding as jest.Mock).mockRejectedValueOnce({ status }); - - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.setState({ - formData: newFormData, - originalData: undefined, - }); - - wrapper.instance().handleSubmit(); - await waitAndUpdate(wrapper); - expect(wrapper.instance().state.originalData).toBeUndefined(); - wrapper.instance().handleReset(); - await waitAndUpdate(wrapper); - expect(wrapper.instance().state.formData).toEqual(newFormData); - }); -}); - -it('should handle field changes', async () => { - const url = 'git.enterprise.com'; - const repository = 'my/repo'; - const instances: AlmSettingsInstance[] = [ - { key: 'instance1', url, alm: AlmKeys.GitHub }, - { key: 'instance2', url, alm: AlmKeys.GitHub }, - { key: 'instance3', url: 'otherurl', alm: AlmKeys.GitHub }, - ]; - (getAlmSettings as jest.Mock).mockResolvedValueOnce(instances); - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - - wrapper.instance().handleFieldChange('key', 'instance2'); - await waitAndUpdate(wrapper); - expect(wrapper.state().formData).toEqual({ - key: 'instance2', - monorepo: false, - }); - - wrapper.instance().handleFieldChange('repository', repository); - await waitAndUpdate(wrapper); - expect(wrapper.state().formData).toEqual({ - key: 'instance2', - repository, - monorepo: false, - }); - - wrapper.instance().handleFieldChange('summaryCommentEnabled', true); - await waitAndUpdate(wrapper); - expect(wrapper.state().formData).toEqual({ - key: 'instance2', - monorepo: false, - repository, - summaryCommentEnabled: true, - }); - - wrapper.instance().handleFieldChange('monorepo', true); - await waitAndUpdate(wrapper); - expect(wrapper.state().formData).toEqual({ - key: 'instance2', - repository, - summaryCommentEnabled: true, - monorepo: true, - }); -}); - -it.each([ - [AlmKeys.Azure, { monorepo: false }], - [AlmKeys.Azure, { slug: 'test', monorepo: false }], - [AlmKeys.Azure, { repository: 'test', monorepo: false }], - [AlmKeys.BitbucketServer, { monorepo: false }], - [AlmKeys.BitbucketServer, { slug: 'test', monorepo: false }], - [AlmKeys.BitbucketServer, { repository: 'test', monorepo: false }], - [AlmKeys.BitbucketCloud, { monorepo: false }], - [AlmKeys.GitHub, { monorepo: false }], - [AlmKeys.GitLab, { monorepo: false }], -])( - 'should properly reject promise for %s & %s', - async (almKey: AlmKeys, params: { monorepo: boolean }) => { - const wrapper = shallowRender(); - - expect.assertions(1); - await expect( - wrapper.instance().submitProjectAlmBinding(almKey, 'binding', params) - ).rejects.toBeUndefined(); - } -); - -it('should validate form', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - - const validateMethod = wrapper.instance().validateForm; - - expect(validateMethod({ key: '', repository: '', monorepo: false })).toBe(false); - expect(validateMethod({ key: '', repository: 'c', monorepo: false })).toBe(false); - - wrapper.setState({ - instances: [ - { key: 'azure', alm: AlmKeys.Azure }, - { key: 'bitbucket', alm: AlmKeys.BitbucketServer }, - { key: 'bitbucketcloud', alm: AlmKeys.BitbucketCloud }, - { key: 'github', alm: AlmKeys.GitHub }, - { key: 'gitlab', alm: AlmKeys.GitLab }, - ], - }); - - [ - { values: { key: 'azure', monorepo: false, repository: 'rep' }, result: false }, - { values: { key: 'azure', monorepo: false, slug: 'project' }, result: false }, - { - values: { key: 'azure', monorepo: false, repository: 'repo', slug: 'project' }, - result: true, - }, - { values: { key: 'github', monorepo: false, repository: '' }, result: false }, - { values: { key: 'github', monorepo: false, repository: 'asdf' }, result: true }, - { values: { key: 'bitbucket', monorepo: false, repository: 'key' }, result: false }, - { - values: { key: 'bitbucket', monorepo: false, repository: 'key', slug: 'slug' }, - result: true, - }, - { values: { key: 'bitbucketcloud', monorepo: false, repository: '' }, result: false }, - { values: { key: 'bitbucketcloud', monorepo: false, repository: 'key' }, result: true }, - { values: { key: 'gitlab', monorepo: false }, result: false }, - { values: { key: 'gitlab', monorepo: false, repository: 'key' }, result: true }, - ].forEach(({ values, result }) => { - expect(validateMethod(values)).toBe(result); - }); -}); - -it('should call the validation WS and store errors', async () => { - (getAlmSettings as jest.Mock).mockResolvedValueOnce(mockAlmSettingsInstance()); - (getProjectAlmBinding as jest.Mock).mockResolvedValueOnce( - mockProjectAlmBindingResponse({ key: 'key' }) - ); - - const errors = mockProjectAlmBindingConfigurationErrors(); - (validateProjectAlmBinding as jest.Mock).mockRejectedValueOnce(errors); - - const wrapper = shallowRender(); - - wrapper.find(PRDecorationBindingRenderer).props().onCheckConfiguration(); - - await waitAndUpdate(wrapper); - - expect(validateProjectAlmBinding).toHaveBeenCalledWith(PROJECT_KEY); - expect(wrapper.state().configurationErrors).toBe(errors); -}); - -it('should call the validation WS after loading', async () => { - (getAlmSettings as jest.Mock).mockResolvedValueOnce([mockAlmSettingsInstance()]); - (getProjectAlmBinding as jest.Mock).mockResolvedValueOnce( - mockProjectAlmBindingResponse({ key: 'key ' }) - ); - - const wrapper = shallowRender(); - - await waitAndUpdate(wrapper); - - expect(validateProjectAlmBinding).toHaveBeenCalled(); -}); - -it('should call the validation WS upon saving', async () => { - (getAlmSettings as jest.Mock).mockResolvedValueOnce([mockAlmSettingsInstance()]); - (getProjectAlmBinding as jest.Mock).mockResolvedValueOnce( - mockProjectAlmBindingResponse({ key: 'key ' }) - ); - - const wrapper = shallowRender(); - - wrapper.instance().handleFieldChange('key', 'key'); - wrapper.instance().handleSubmit(); - - await waitAndUpdate(wrapper); - - expect(validateProjectAlmBinding).toHaveBeenCalled(); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx deleted file mode 100644 index 99acf51f9ee..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx +++ /dev/null @@ -1,149 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { - AlmKeys, - AlmSettingsInstance, - ProjectAlmBindingConfigurationErrors, - ProjectAlmBindingConfigurationErrorScope, -} from '../../../../../types/alm-settings'; -import PRDecorationBindingRenderer, { - PRDecorationBindingRendererProps, -} from '../PRDecorationBindingRenderer'; - -const urls = ['http://github.enterprise.com', 'http://bbs.enterprise.com']; -const instances: AlmSettingsInstance[] = [ - { - alm: AlmKeys.GitHub, - key: 'i1', - url: urls[0], - }, - { - alm: AlmKeys.GitHub, - key: 'i2', - url: urls[0], - }, - { - alm: AlmKeys.BitbucketServer, - key: 'i3', - url: urls[1], - }, - { - alm: AlmKeys.Azure, - key: 'i4', - }, -]; -const configurationErrors: ProjectAlmBindingConfigurationErrors = { - scope: ProjectAlmBindingConfigurationErrorScope.Global, - errors: [{ msg: 'Test' }, { msg: 'tesT' }], -}; - -it.each([ - ['when loading', { loading: true }], - ['with no ALM instances (admin user)', { isSysAdmin: true }], - ['with no ALM instances (non-admin user)', {}], - ['with a single ALM instance', { instances: [instances[0]] }], - ['with an empty form', { instances }], - [ - 'with a valid and saved form', - { - formData: { - key: 'i1', - repository: 'account/repo', - monorepo: false, - }, - isChanged: false, - isConfigured: true, - instances, - }, - ], - [ - 'when there are configuration errors (non-admin user)', - { instances, isConfigured: true, configurationErrors }, - ], - [ - 'when there are configuration errors (admin user)', - { - formData: { - key: 'i1', - repository: 'account/repo', - monorepo: false, - }, - instances, - isConfigured: true, - configurationErrors, - isSysAdmin: true, - }, - ], - [ - 'when there are configuration errors (admin user) and error are at PROJECT level', - { - instances, - isConfigured: true, - configurationErrors: { - ...configurationErrors, - scope: ProjectAlmBindingConfigurationErrorScope.Project, - }, - isSysAdmin: true, - }, - ], -])('should render correctly', (name: string, props: PRDecorationBindingRendererProps) => { - expect(shallowRender(props)).toMatchSnapshot(name); -}); - -it.each([ - ['updating', { updating: true }], - ['update is successfull', { successfullyUpdated: true }], - ['form is valid', { isValid: true }], - ['configuration is saved', { isConfigured: true }], - ['configuration check is in progress', { isConfigured: true, checkingConfiguration: true }], -])( - 'should display action section correctly when', - (name: string, props: PRDecorationBindingRendererProps) => { - expect(shallowRender({ ...props, instances }).find('.action-section')).toMatchSnapshot(name); - } -); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap deleted file mode 100644 index 555d867cbe7..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap +++ /dev/null @@ -1,834 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly for azure 1`] = ` - -
-
- -
- -
- example - : - - - My Project - - -
-
-
-
- -
-
-
-
- -
- -
- example - : - - - My Repository - - -
-
-
-
- -
-
-
-`; - -exports[`should render correctly for bitbucket 1`] = ` - -
-
- -
- -
- example - : - - https://bb.company.com/projects/ - - MY_PROJECT_KEY - - /repos/my-repository-slug/browse - -
-
-
-
- -
-
-
-
- -
- -
- example - : - - https://bb.company.com/projects/MY_PROJECT_KEY/repos/ - - my-repository-slug - - /browse - -
-
-
-
- -
-
-
-`; - -exports[`should render correctly for bitbucket if an instance URL is provided 1`] = ` - -
-
- -
- -
- example - : - - http://bbs.example.com/projects/ - - MY_PROJECT_KEY - - /repos/my-repository-slug/browse - -
-
-
-
- -
-
-
-
- -
- -
- example - : - - http://bbs.example.com/projects/MY_PROJECT_KEY/repos/ - - my-repository-slug - - /browse - -
-
-
-
- -
-
-
-`; - -exports[`should render correctly for bitbucketcloud 1`] = ` - -
-
- -
- -
- example - : - - https://bitbucket.org/my-workspace/ - - my-repository-slug - - -
-
-
-
- -
-
-
-`; - -exports[`should render correctly for github 1`] = ` - -
-
- -
- -
- example - : - - https://github.com/ - - sonarsource/sonarqube - - -
-
-
-
- -
-
-
-
- -
- -
-
-
-
-
- -
-
-
-
-
-`; - -exports[`should render correctly for github if an instance URL is provided 1`] = ` - -
-
- -
- -
- example - : - - http://example.com/ - - sonarsource/sonarqube - - -
-
-
-
- -
-
-
-
- -
- -
-
-
-
-
- -
-
-
-
-
-`; - -exports[`should render correctly for github if an instance URL is provided 2`] = ` - -
-
- -
- -
- example - : - - https://github.com/ - - sonarsource/sonarqube - - -
-
-
-
- -
-
-
-
- -
- -
-
-
-
-
- -
-
-
-
-
-`; - -exports[`should render correctly for gitlab 1`] = ` - -
-
- -
- -
- example - : - - - 123456 - - -
-
-
-
- -
-
-
-`; - -exports[`should render the monorepo field when the feature is supported 1`] = ` - -
-
- -
- -
- example - : - - - My Project - - -
-
-
-
- -
-
-
-
- -
- -
- example - : - - - My Repository - - -
-
-
-
- -
-
-
-
- -
- - learn_more - , - } - } - /> -
-
-
-
-
- -
-
-
-
-
-`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap deleted file mode 100644 index 53dc3641152..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` - -`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap deleted file mode 100644 index 0bd144c7702..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap +++ /dev/null @@ -1,870 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should display action section correctly when: configuration check is in progress 1`] = ` -
- - -
-`; - -exports[`should display action section correctly when: configuration is saved 1`] = ` -
- - -
-`; - -exports[`should display action section correctly when: form is valid 1`] = ` -
-`; - -exports[`should display action section correctly when: update is successfull 1`] = ` -
- - - settings.state.saved - -
-`; - -exports[`should display action section correctly when: updating 1`] = ` -
-`; - -exports[`should render correctly: when loading 1`] = ``; - -exports[`should render correctly: when there are configuration errors (admin user) 1`] = ` -
-
-

- settings.pr_decoration.binding.title -

-
-
- settings.pr_decoration.binding.description -
-
- -
-
- -
- settings.pr_decoration.binding.form.name.help -
-
-
- -
-
- -
- - -
- -

- settings.pr_decoration.binding.check_configuration.failure -

-
    -
  • - Test -
  • -
  • - tesT -
  • -
-

- - settings.pr_decoration.binding.check_configuration.failure.check_global_settings.link - , - } - } - /> -

-
- -
-`; - -exports[`should render correctly: when there are configuration errors (admin user) and error are at PROJECT level 1`] = ` -
-
-

- settings.pr_decoration.binding.title -

-
-
- settings.pr_decoration.binding.description -
-
- -
-
- -
- settings.pr_decoration.binding.form.name.help -
-
-
- -
-
-
- - -
- -

- settings.pr_decoration.binding.check_configuration.failure -

-
    -
  • - Test -
  • -
  • - tesT -
  • -
-
- -
-`; - -exports[`should render correctly: when there are configuration errors (non-admin user) 1`] = ` -
-
-

- settings.pr_decoration.binding.title -

-
-
- settings.pr_decoration.binding.description -
-
- -
-
- -
- settings.pr_decoration.binding.form.name.help -
-
-
- -
-
-
- - -
- -

- settings.pr_decoration.binding.check_configuration.failure -

-
    -
  • - Test -
  • -
  • - tesT -
  • -
-

- settings.pr_decoration.binding.check_configuration.contact_admin -

-
- -
-`; - -exports[`should render correctly: with a single ALM instance 1`] = ` -
-
-

- settings.pr_decoration.binding.title -

-
-
- settings.pr_decoration.binding.description -
-
- -
-
- -
- settings.pr_decoration.binding.form.name.help -
-
-
- -
-
-
- -
-`; - -exports[`should render correctly: with a valid and saved form 1`] = ` -
-
-

- settings.pr_decoration.binding.title -

-
-
- settings.pr_decoration.binding.description -
-
- -
-
- -
- settings.pr_decoration.binding.form.name.help -
-
-
- -
-
- -
- - -
- - settings.pr_decoration.binding.check_configuration.success - - -
-`; - -exports[`should render correctly: with an empty form 1`] = ` -
-
-

- settings.pr_decoration.binding.title -

-
-
- settings.pr_decoration.binding.description -
-
- -
-
- -
- settings.pr_decoration.binding.form.name.help -
-
-
- -
-
-
- -
-`; - -exports[`should render correctly: with no ALM instances (admin user) 1`] = ` -
- - - settings.pr_decoration.binding.no_bindings.link - , - } - } - /> - -
-`; - -exports[`should render correctly: with no ALM instances (non-admin user) 1`] = ` -
- - settings.pr_decoration.binding.no_bindings - -
-`; diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/EncryptionApp-it.tsx b/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/EncryptionApp-it.tsx index e40db8f9ce1..40fad0bf432 100644 --- a/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/EncryptionApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/EncryptionApp-it.tsx @@ -47,24 +47,26 @@ const ui = { }; it('should be able to generate new key', async () => { + const user = userEvent.setup(); renderEncryptionApp(); expect(await ui.appHeading.find()).toBeInTheDocument(); - await userEvent.click(ui.generateSecretButton.get()); + await user.click(ui.generateSecretButton.get()); expect(ui.copyToClipboard.get()).toHaveAttribute('data-clipboard-text', 'secretKey'); }); it('should be able to encrypt property value when secret is registered', async () => { + const user = userEvent.setup(); settingsMock.setSecretKeyAvailable(true); renderEncryptionApp(); expect(await ui.appHeading.find()).toBeInTheDocument(); - await userEvent.type(ui.encryptTextarea.get(), 'sonar.announcement.message'); - await userEvent.click(ui.encryptButton.get()); + await user.type(ui.encryptTextarea.get(), 'sonar.announcement.message'); + await user.click(ui.encryptButton.get()); expect(ui.copyToClipboard.get()).toHaveAttribute('data-clipboard-text', 'encryptedValue'); // can generate new secret in view - await userEvent.click(ui.generateNewSecretButton.get()); + await user.click(ui.generateNewSecretButton.get()); expect(ui.copyToClipboard.get()).toHaveAttribute('data-clipboard-text', 'secretKey'); }); diff --git a/server/sonar-web/src/main/js/components/controls/Toggle.tsx b/server/sonar-web/src/main/js/components/controls/Toggle.tsx index 008c393d260..3134c7e24b5 100644 --- a/server/sonar-web/src/main/js/components/controls/Toggle.tsx +++ b/server/sonar-web/src/main/js/components/controls/Toggle.tsx @@ -24,6 +24,7 @@ import { Button } from './buttons'; import './Toggle.css'; interface Props { + id?: string; ariaLabel?: string; disabled?: boolean; name?: string; @@ -49,12 +50,13 @@ export default class Toggle extends React.PureComponent { }; render() { - const { ariaLabel, disabled, name } = this.props; + const { ariaLabel, disabled, name, id } = this.props; const value = this.getValue(); const className = classNames('boolean-toggle', { 'boolean-toggle-on': value }); return (