diff options
Diffstat (limited to 'server/sonar-web')
37 files changed, 348 insertions, 285 deletions
diff --git a/server/sonar-web/design-system/package.json b/server/sonar-web/design-system/package.json index 33aa253317c..a237ff598ca 100644 --- a/server/sonar-web/design-system/package.json +++ b/server/sonar-web/design-system/package.json @@ -25,8 +25,8 @@ "@emotion/babel-plugin-jsx-pragmatic": "0.2.1", "@sonarsource/echoes-react": "0.6.0", "@testing-library/dom": "10.2.0", - "@testing-library/jest-dom": "6.4.6", - "@testing-library/react": "16.0.0", + "@testing-library/jest-dom": "6.5.0", + "@testing-library/react": "16.0.1", "@testing-library/user-event": "14.5.2", "@types/d3-array": "3.2.1", "@types/d3-hierarchy": "~3.1.7", diff --git a/server/sonar-web/design-system/src/sonar-aligned/components/input/SelectCommon.tsx b/server/sonar-web/design-system/src/sonar-aligned/components/input/SelectCommon.tsx index 8414e6f3b79..8e89171aeb7 100644 --- a/server/sonar-web/design-system/src/sonar-aligned/components/input/SelectCommon.tsx +++ b/server/sonar-web/design-system/src/sonar-aligned/components/input/SelectCommon.tsx @@ -61,9 +61,13 @@ export function IconOption< const { label, isSelected } = props; const { Icon } = props.data as { Icon: JSX.Element }; + // For tests and a11y + props.innerProps.role = 'option'; + props.innerProps['aria-selected'] = isSelected; + return ( <components.Option {...props}> - <div aria-selected={isSelected} className="sw-flex sw-items-center sw-gap-1" role="option"> + <div className="sw-flex sw-items-center sw-gap-1"> {Icon} <SearchHighlighter>{label}</SearchHighlighter> </div> diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index 5dfb4282fc8..e8f1d9f5a42 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -55,9 +55,9 @@ "@jupyterlab/nbformat": "4.2.4", "@swc/core": "1.6.6", "@swc/jest": "0.2.36", - "@testing-library/dom": "9.3.4", - "@testing-library/jest-dom": "6.4.6", - "@testing-library/react": "14.2.1", + "@testing-library/dom": "10.2.0", + "@testing-library/jest-dom": "6.5.0", + "@testing-library/react": "16.0.1", "@testing-library/user-event": "14.5.2", "@types/cheerio": "0.22.35", "@types/classnames": "2.3.1", @@ -117,7 +117,6 @@ "postcss-custom-properties": "12.1.11", "prettier": "3.3.2", "prettier-plugin-organize-imports": "3.2.4", - "react-select-event": "5.5.1", "tailwindcss": "3.4.4", "turbo": "1.11.3", "typescript": "5.5.3", @@ -144,7 +143,7 @@ "dep-check": "node scripts/validate-package-json.js" }, "engines": { - "node": ">=14" + "node": ">=18.20" }, "browser": { "path": "path-browserify" diff --git a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx index 6b24fdf711f..16f6bd6d9b4 100644 --- a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx +++ b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx @@ -22,7 +22,6 @@ import userEvent from '@testing-library/user-event'; import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup'; import React from 'react'; import { Outlet, Route } from 'react-router-dom'; -import selectEvent from 'react-select-event'; import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import { getMyProjects, getScannableProjects } from '../../../api/components'; import NotificationsMock from '../../../api/mocks/NotificationsMock'; @@ -296,24 +295,23 @@ describe('security page', () => { // eslint-disable-next-line jest/no-conditional-in-test if (tokenTypeOption === TokenType.Project) { - await selectEvent.select(screen.getByRole('combobox', { name: 'users.tokens.type' }), [ - tokenTypeLabel, - ]); + await user.click(ui.tokenTypeSelect.get()); + await user.click(byRole('option', { name: tokenTypeLabel }).get()); + // eslint-disable-next-line jest/no-conditional-expect expect(generateButton).toBeDisabled(); // eslint-disable-next-line jest/no-conditional-expect expect(screen.getByRole('textbox', { name: 'users.tokens.name' })).toBeInTheDocument(); // eslint-disable-next-line jest/no-conditional-expect expect(screen.getAllByRole('combobox')).toHaveLength(3); - await selectEvent.select(screen.getByRole('combobox', { name: 'users.tokens.project' }), [ - 'Project Name 1', - ]); + + await user.click(ui.projectSelect.get()); + await user.click(byRole('option', { name: 'Project Name 1' }).get()); // eslint-disable-next-line jest/no-conditional-expect expect(generateButton).toBeEnabled(); } else { - await selectEvent.select(screen.getByRole('combobox', { name: 'users.tokens.type' }), [ - tokenTypeLabel, - ]); + await user.click(ui.tokenTypeSelect.get()); + await user.click(byRole('option', { name: tokenTypeLabel }).get()); // eslint-disable-next-line jest/no-conditional-expect expect(generateButton).toBeEnabled(); } @@ -393,6 +391,7 @@ describe('security page', () => { }); it("should not suggest creating a Project token if the user doesn't have at least one scannable Projects", async () => { + const user = userEvent.setup(); jest.mocked(getScannableProjects).mockResolvedValueOnce({ projects: [], }); @@ -403,7 +402,7 @@ describe('security page', () => { expect(await screen.findByText('users.tokens.generate')).toBeInTheDocument(); - await selectEvent.openMenu(screen.getByRole('combobox', { name: 'users.tokens.type' })); + await user.click(ui.tokenTypeSelect.get()); expect(screen.queryByText(`users.tokens.${TokenType.Project}`)).not.toBeInTheDocument(); }); @@ -418,6 +417,7 @@ describe('security page', () => { }); it('should preselect the only project the user has access to if they select project token', async () => { + const user = userEvent.setup(); jest.mocked(getScannableProjects).mockResolvedValueOnce({ projects: [ { @@ -431,9 +431,9 @@ describe('security page', () => { securityPagePath, ); expect(await screen.findByText('users.tokens.generate')).toBeInTheDocument(); - await selectEvent.select(screen.getByRole('combobox', { name: 'users.tokens.type' }), [ - `users.tokens.${TokenType.Project}`, - ]); + + await user.click(ui.tokenTypeSelect.get()); + await user.click(byRole('option', { name: `users.tokens.${TokenType.Project}` }).get()); expect(screen.getByText('Project Name 1')).toBeInTheDocument(); }); @@ -696,3 +696,8 @@ function renderAccountApp(currentUser: CurrentUser, navigateTo?: string) { { currentUser, navigateTo }, ); } + +const ui = { + tokenTypeSelect: byRole('combobox', { name: 'users.tokens.type' }), + projectSelect: byRole('combobox', { name: 'users.tokens.project' }), +}; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx b/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx index 338205bfae1..40626b9c301 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx @@ -19,7 +19,6 @@ */ import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import selectEvent from 'react-select-event'; import { byLabelText, byPlaceholderText, @@ -354,7 +353,9 @@ function getPageObject() { }, async changeTaskFilter(fieldLabel: string, value: string) { - await selectEvent.select(screen.getByRole('combobox', { name: fieldLabel }), [value]); + await user.click(byRole('combobox', { name: fieldLabel }).get()); + await user.click(byRole('option', { name: value }).get()); + expect(await screen.findByRole('button', { name: 'reload' })).toBeEnabled(); }, diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts index d17faa7d4ed..6fdf75b1707 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts +++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts @@ -18,12 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { fireEvent, screen, within } from '@testing-library/react'; -import selectEvent from 'react-select-event'; import CodingRulesServiceMock, { RULE_TAGS_MOCK } from '../../../api/mocks/CodingRulesServiceMock'; import SettingsServiceMock from '../../../api/mocks/SettingsServiceMock'; import { QP_2, RULE_1, RULE_10, RULE_9 } from '../../../api/mocks/data/ids'; import { CLEAN_CODE_CATEGORIES, SOFTWARE_QUALITIES } from '../../../helpers/constants'; import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks'; +import { byRole } from '../../../sonar-aligned/helpers/testSelector'; import { CleanCodeAttribute, CleanCodeAttributeCategory, @@ -114,12 +114,15 @@ describe('Rules app list', () => { const monthSelector = within(ui.dateInputMonthSelect.get()).getByRole('combobox'); await user.click(monthSelector); - await selectEvent.select(ui.dateInputMonthSelect.byRole('combobox').get(), 'Nov'); + await user.click(ui.dateInputMonthSelect.byRole('combobox').get()); + await user.click(byRole('option', { name: 'Nov' }).get()); const yearSelector = within(ui.dateInputYearSelect.get()).getByRole('combobox'); await user.click(yearSelector); - await selectEvent.select(ui.dateInputYearSelect.byRole('combobox').get(), '2022'); + await user.click(ui.dateInputYearSelect.byRole('combobox').get()); + await user.click(byRole('option', { name: '2022' }).get()); + await user.click(await screen.findByText('1', { selector: 'button' })); expect(ui.getAllRuleListItems()).toHaveLength(1); @@ -351,7 +354,9 @@ describe('Rules app list', () => { await user.click(ui.activateButton.getAll()[0]); expect(ui.selectValue.get(ui.activateQPDialog.get())).toHaveTextContent('severity.MAJOR'); expect(ui.prioritizedSwitch.get(ui.activateQPDialog.get())).not.toBeChecked(); - await selectEvent.select(ui.oldSeveritySelect.get(), 'severity.MINOR'); + await user.click(ui.oldSeveritySelect.get()); + await user.click(byRole('option', { name: 'severity.MINOR' }).get()); + await user.click(ui.prioritizedSwitch.get(ui.activateQPDialog.get())); await user.click(ui.activateButton.get(ui.activateQPDialog.get())); @@ -363,7 +368,8 @@ describe('Rules app list', () => { await user.click(ui.changeButton('QP Bar').get()); expect(ui.selectValue.get(ui.changeQPDialog.get())).toHaveTextContent('severity.MINOR'); expect(ui.prioritizedSwitch.get(ui.changeQPDialog.get())).toBeChecked(); - await selectEvent.select(ui.oldSeveritySelect.get(), 'severity.BLOCKER'); + await user.click(ui.oldSeveritySelect.get()); + await user.click(byRole('option', { name: 'severity.BLOCKER' }).get()); await user.click(ui.prioritizedSwitch.get(ui.changeQPDialog.get())); await user.click(ui.saveButton.get(ui.changeQPDialog.get())); @@ -642,7 +648,7 @@ describe('Rule app details', () => { // Activate rule in quality profile expect(ui.prioritizedRuleCell.query()).not.toBeInTheDocument(); await user.click(ui.activateButton.get()); - await selectEvent.select(ui.qualityProfileSelect.get(), 'QP FooBar'); + await user.click(ui.prioritizedSwitch.get()); await user.click(ui.activateButton.get(ui.activateQPDialog.get())); expect(ui.qpLink('QP FooBar').get()).toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts index 8a89d79ea62..208cc1a0b98 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts +++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts @@ -17,8 +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 selectEvent from 'react-select-event'; -import { byText } from '~sonar-aligned/helpers/testSelector'; +import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import CodingRulesServiceMock from '../../../api/mocks/CodingRulesServiceMock'; import SettingsServiceMock from '../../../api/mocks/SettingsServiceMock'; import { mockLoggedInUser } from '../../../helpers/testMocks'; @@ -58,19 +57,19 @@ describe('custom rule', () => { await user.clear(ui.keyTextbox.get()); await user.type(ui.keyTextbox.get(), 'new_custom_rule'); - await selectEvent.select( - ui.cleanCodeCategorySelect.get(), - 'rule.clean_code_attribute_category.CONSISTENT', - ); - await selectEvent.select( - ui.cleanCodeAttributeSelect.get(), - 'rule.clean_code_attribute.IDENTIFIABLE', + await user.click(ui.cleanCodeCategorySelect.get()); + await user.click( + byRole('option', { name: 'rule.clean_code_attribute_category.CONSISTENT' }).get(), ); - await selectEvent.select( - ui.cleanCodeCategorySelect.get(), - 'rule.clean_code_attribute_category.INTENTIONAL', + await user.click(ui.cleanCodeAttributeSelect.get()); + await user.click(byRole('option', { name: 'rule.clean_code_attribute.IDENTIFIABLE' }).get()); + + await user.click(ui.cleanCodeCategorySelect.get()); + await user.click( + byRole('option', { name: 'rule.clean_code_attribute_category.INTENTIONAL' }).get(), ); + // Setting default clean code category of a template should set corresponding attribute expect( ui.createCustomRuleDialog.byText('rule.clean_code_attribute.CLEAR').get(), @@ -85,13 +84,14 @@ describe('custom rule', () => { ).toBeInTheDocument(); await user.click(ui.cleanCodeQualityCheckbox(SoftwareQuality.Reliability).get()); - await selectEvent.select( - ui.cleanCodeSeveritySelect(SoftwareQuality.Reliability).get(), - 'severity.MEDIUM', - ); + + await user.click(ui.cleanCodeSeveritySelect(SoftwareQuality.Reliability).get()); + await user.click(byRole('option', { name: 'severity.MEDIUM severity.MEDIUM' }).get()); + expect(ui.createCustomRuleDialog.byText('severity.MEDIUM').get()).toBeInTheDocument(); - await selectEvent.select(ui.statusSelect.get(), 'rules.status.BETA'); + await user.click(ui.statusSelect.get()); + await user.click(byRole('option', { name: 'rules.status.BETA' }).get()); await user.type(ui.descriptionTextbox.get(), 'Some description for custom rule'); await user.type(ui.paramInput('1').get(), 'Default value'); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/SeveritySelect.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/SeveritySelect.tsx index f6bd9792ed5..6aa134c43cc 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/SeveritySelect.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/SeveritySelect.tsx @@ -32,6 +32,10 @@ export interface SeveritySelectProps { } function Option(props: Readonly<OptionProps<LabelValueSelectOption<IssueSeverity>, false>>) { + // For tests and a11y + props.innerProps.role = 'option'; + props.innerProps['aria-selected'] = props.isSelected; + return ( <components.Option {...props}> <SeverityHelper className="sw-flex sw-items-center" severity={props.data.value} /> diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx index af0547a00da..358d7db3b8b 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx @@ -18,10 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { screen, waitFor } 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 '~sonar-aligned/helpers/testSelector'; import { searchAzureRepositories } from '../../../../api/alm-integrations'; import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; @@ -90,7 +88,8 @@ it('should ask for PAT when it is not set yet and show the import project featur expect(await screen.findByText('onboarding.create_project.azure.title')).toBeInTheDocument(); expect(screen.getByText('alm.configuration.selector.label.alm.azure.long')).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-azure-1/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-azure-1/ }).get()); expect(await screen.findByText('onboarding.create_project.enter_pat')).toBeInTheDocument(); expect(screen.getByText('onboarding.create_project.pat_form.title')).toBeInTheDocument(); @@ -112,7 +111,8 @@ it('should show import project feature when PAT is already set', async () => { renderCreateProject(); expect(await screen.findByText('onboarding.create_project.azure.title')).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-azure-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-azure-2/ }).get()); expect(await screen.findByText('Azure project')).toBeInTheDocument(); expect(screen.getByText('Azure project 2')).toBeInTheDocument(); @@ -194,7 +194,8 @@ it('should show search filter when PAT is already set', async () => { renderCreateProject(); expect(await screen.findByText('onboarding.create_project.azure.title')).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-azure-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-azure-2/ }).get()); // Should search with positive results const inputSearch = await screen.findByPlaceholderText( @@ -241,10 +242,13 @@ describe('Azure monorepo setup navigation', () => { }); it('should load every repositories from every projects in monorepo setup mode', async () => { + const user = userEvent.setup(); renderCreateProject({ isMonorepo: true }); - await selectEvent.select(await ui.monorepoDopSettingDropdown.find(), [/conf-azure-2/]); - selectEvent.openMenu(await ui.repositorySelector.find()); + await user.click(await ui.monorepoDopSettingDropdown.find()); + await user.click(byRole('option', { name: /conf-azure-2/ }).get()); + + await user.click(ui.repositorySelector.get()); expect(screen.getByText('Azure repo 1')).toBeInTheDocument(); expect(screen.getByText('Azure repo 2')).toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx index 72bf92af621..d71369d4d9f 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx @@ -21,7 +21,6 @@ import { screen, waitFor, 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 '~sonar-aligned/helpers/testSelector'; import { searchForBitbucketServerRepositories } from '../../../../api/alm-integrations'; import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; @@ -85,7 +84,9 @@ it('should ask for PAT when it is not set yet and show the import project featur expect(screen.getByText('onboarding.create_project.bitbucket.title')).toBeInTheDocument(); expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketserver-1/]); + + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-bitbucketserver-1/ }).get()); expect(await screen.findByText('onboarding.create_project.pat_form.title')).toBeInTheDocument(); @@ -113,7 +114,8 @@ it('should show import project feature when PAT is already set', async () => { expect(screen.getByText('onboarding.create_project.bitbucket.title')).toBeInTheDocument(); expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketserver-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-bitbucketserver-2/ }).get()); expect(await screen.findByText('Bitbucket Project 1')).toBeInTheDocument(); @@ -167,7 +169,8 @@ it('should show search filter when PAT is already set', async () => { expect(screen.getByText('onboarding.create_project.bitbucket.title')).toBeInTheDocument(); expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketserver-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-bitbucketserver-2/ }).get()); const inputSearch = await screen.findByRole('searchbox', { name: 'onboarding.create_project.search_repositories_by_name', @@ -184,12 +187,14 @@ it('should show search filter when PAT is already set', async () => { }); it('should show no result message when there are no projects', async () => { + const user = userEvent.setup(); almIntegrationHandler.setBitbucketServerProjects([]); renderCreateProject(); expect(screen.getByText('onboarding.create_project.bitbucket.title')).toBeInTheDocument(); expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketserver-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-bitbucketserver-2/ }).get()); expect(await screen.findByText('onboarding.create_project.no_bbs_projects')).toBeInTheDocument(); }); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx index e900b099e07..75046e169d1 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx @@ -21,7 +21,6 @@ import { screen, waitFor, 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 '~sonar-aligned/helpers/testSelector'; import { searchForBitbucketCloudRepositories } from '../../../../api/alm-integrations'; import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; @@ -96,7 +95,8 @@ it('should ask for PAT when it is not set yet and show the import project featur expect(screen.getByText('onboarding.create_project.bitbucketcloud.title')).toBeInTheDocument(); expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketcloud-1/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-bitbucketcloud-1/ }).get()); expect( await screen.findByText('onboarding.create_project.bitbucket_cloud.enter_password'), @@ -138,7 +138,8 @@ it('should show import project feature when PAT is already set', async () => { expect(screen.getByText('onboarding.create_project.bitbucketcloud.title')).toBeInTheDocument(); expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketcloud-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-bitbucketcloud-2/ }).get()); expect(await screen.findByText('BitbucketCloud Repo 1')).toBeInTheDocument(); expect(screen.getByText('BitbucketCloud Repo 2')).toBeInTheDocument(); @@ -184,7 +185,8 @@ it('should show search filter when PAT is already set', async () => { expect(screen.getByText('onboarding.create_project.bitbucketcloud.title')).toBeInTheDocument(); expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketcloud-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-bitbucketcloud-2/ }).get()); await waitFor(() => expect(searchForBitbucketCloudRepositories).toHaveBeenLastCalledWith( @@ -212,13 +214,15 @@ it('should show search filter when PAT is already set', async () => { }); it('should show no result message when there are no projects', async () => { + const user = userEvent.setup(); almIntegrationHandler.setBitbucketCloudRepositories([]); renderCreateProject(); expect(screen.getByText('onboarding.create_project.bitbucketcloud.title')).toBeInTheDocument(); expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketcloud-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-bitbucketcloud-2/ }).get()); expect( await screen.findByText('onboarding.create_project.bitbucketcloud.no_projects'), @@ -236,7 +240,8 @@ it('should have load more', async () => { expect(screen.getByText('onboarding.create_project.bitbucketcloud.title')).toBeInTheDocument(); expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketcloud-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-bitbucketcloud-2/ }).get()); expect(await screen.findByRole('button', { name: 'show_more' })).toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx index e231ed99b3c..2ed43d55d56 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx @@ -21,7 +21,6 @@ import { screen, waitFor } 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 '~sonar-aligned/helpers/testSelector'; import { getGithubRepositories } from '../../../../api/alm-integrations'; import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; @@ -92,25 +91,29 @@ afterAll(() => { }); it('should redirect to github authorization page when not already authorized', async () => { + const user = userEvent.setup(); renderCreateProject('project/create?mode=github'); expect(await screen.findByText('onboarding.create_project.github.title')).toBeInTheDocument(); expect(screen.getByText('alm.configuration.selector.placeholder')).toBeInTheDocument(); expect(ui.instanceSelector.get()).toBeInTheDocument(); - await selectEvent.select(await ui.instanceSelector.find(), [/conf-github-1/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-github-1/ }).get()); expect(window.location.replace).toHaveBeenCalled(); }); it('should not redirect to github when url is malformated', async () => { + const user = userEvent.setup(); renderCreateProject('project/create?mode=github'); expect(await screen.findByText('onboarding.create_project.github.title')).toBeInTheDocument(); expect(screen.getByText('alm.configuration.selector.placeholder')).toBeInTheDocument(); expect(ui.instanceSelector.get()).toBeInTheDocument(); - await waitFor(() => selectEvent.select(ui.instanceSelector.get(), [/conf-github-3/])); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-github-3/ }).get()); expect(await ui.createErrorMessage.find()).toBeInTheDocument(); @@ -124,7 +127,8 @@ it('should show import project feature when the authentication is successfull', expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await waitFor(() => selectEvent.select(ui.organizationSelector.get(), [/org-1/])); + await user.click(ui.organizationSelector.get()); + await user.click(byRole('option', { name: /org-1/ }).get()); expect(await ui.project1.find()).toBeInTheDocument(); expect(ui.project2.get()).toBeInTheDocument(); @@ -176,7 +180,8 @@ it('should import several projects', async () => { expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await waitFor(() => selectEvent.select(ui.organizationSelector.get(), [/org-1/])); + await user.click(ui.organizationSelector.get()); + await user.click(byRole('option', { name: /org-1/ }).get()); expect(await ui.project1.find()).toBeInTheDocument(); expect(ui.project1Checkbox.get()).not.toBeChecked(); @@ -241,7 +246,8 @@ it('should show search filter when the authentication is successful', async () = expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await waitFor(() => selectEvent.select(ui.organizationSelector.get(), [/org-1/])); + await user.click(ui.organizationSelector.get()); + await user.click(byRole('option', { name: /org-1/ }).get()); const inputSearch = screen.getByRole('searchbox'); await user.click(inputSearch); @@ -266,7 +272,8 @@ it('should have load more', async () => { expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await waitFor(() => selectEvent.select(ui.organizationSelector.get(), [/org-1/])); + await user.click(ui.organizationSelector.get()); + await user.click(byRole('option', { name: /org-1/ }).get()); const loadMore = await screen.findByRole('button', { name: 'show_more' }); expect(loadMore).toBeInTheDocument(); @@ -288,13 +295,15 @@ it('should have load more', async () => { }); it('should show no result message when there are no projects', async () => { + const user = userEvent.setup(); almIntegrationHandler.setGithubRepositories([]); renderCreateProject('project/create?mode=github&dopSetting=conf-github-2&code=213321213'); expect(await ui.instanceSelector.find()).toBeInTheDocument(); - await waitFor(() => selectEvent.select(ui.organizationSelector.get(), [/org-1/])); + await user.click(ui.organizationSelector.get()); + await user.click(byRole('option', { name: /org-1/ }).get()); expect(screen.getByText('no_results')).toBeInTheDocument(); }); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx index b6d08bb85a1..04a2ead7fb7 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx @@ -20,7 +20,6 @@ import { screen, waitFor } 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 '~sonar-aligned/helpers/testSelector'; import { getGitlabProjects } from '../../../../api/alm-integrations'; import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; @@ -113,7 +112,8 @@ it('should ask for PAT when it is not set yet and show the import project featur expect(await ui.importProjectsTitle.find()).toBeInTheDocument(); expect(ui.instanceSelector.get()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-final-1/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-final-1/ }).get()); expect(await screen.findByText('onboarding.create_project.enter_pat')).toBeInTheDocument(); expect(ui.patHelpInstructions.get()).toBeInTheDocument(); @@ -127,10 +127,12 @@ it('should ask for PAT when it is not set yet and show the import project featur }); it('should show import project feature when PAT is already set', async () => { + const user = userEvent.setup(); renderCreateProject(); expect(await ui.importProjectsTitle.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-final-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-final-2/ }).get()); expect(await ui.project1.find()).toBeInTheDocument(); expect(ui.project1Link.get()).toHaveAttribute('href', '/dashboard?id=key'); @@ -146,7 +148,8 @@ it('should show search filter when PAT is already set', async () => { expect(await ui.importProjectsTitle.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-final-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-final-2/ }).get()); const inputSearch = await screen.findByRole('searchbox'); await user.click(inputSearch); @@ -173,7 +176,8 @@ it('should import several projects', async () => { renderCreateProject(); expect(await ui.importProjectsTitle.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-final-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-final-2/ }).get()); expect(await ui.project1.find()).toBeInTheDocument(); expect(ui.project1Checkbox.get()).not.toBeChecked(); @@ -237,7 +241,9 @@ it('should have load more', async () => { almIntegrationHandler.createRandomGitlabProjectsWithLoadMore(50, 75); renderCreateProject(); - await selectEvent.select(await ui.instanceSelector.find(), [/conf-final-2/]); + await user.click(await ui.instanceSelector.find()); + await user.click(byRole('option', { name: /conf-final-2/ }).get()); + const loadMore = await screen.findByRole('button', { name: 'show_more' }); expect(loadMore).toBeInTheDocument(); @@ -257,11 +263,13 @@ it('should have load more', async () => { }); it('should show no result message when there are no projects', async () => { + const user = userEvent.setup(); almIntegrationHandler.setGitlabProjects([]); renderCreateProject(); expect(await ui.importProjectsTitle.find()).toBeInTheDocument(); - await selectEvent.select(ui.instanceSelector.get(), [/conf-final-2/]); + await user.click(ui.instanceSelector.get()); + await user.click(byRole('option', { name: /conf-final-2/ }).get()); expect(await screen.findByText('no_results')).toBeInTheDocument(); }); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/MonorepoProjectCreate-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/MonorepoProjectCreate-it.tsx index 2fdd09661d3..2d03e74765b 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/MonorepoProjectCreate-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/MonorepoProjectCreate-it.tsx @@ -20,7 +20,6 @@ import { waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import selectEvent from 'react-select-event'; import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; @@ -124,6 +123,7 @@ describe('github monorepo project setup', () => { }); it('should display that selected repository is not bound to any existing project', async () => { + const user = userEvent.setup(); renderCreateProject({ code: '123', dopSetting: 'dop-setting-test-id', isMonorepo: true }); expect(await ui.monorepoTitle.find()).toBeInTheDocument(); @@ -131,17 +131,19 @@ describe('github monorepo project setup', () => { expect(await ui.dopSettingSelector.find()).toBeInTheDocument(); expect(ui.monorepoProjectTitle.query()).not.toBeInTheDocument(); - await waitFor(async () => { - await selectEvent.select(await ui.organizationSelector.find(), 'org-1'); - }); + await user.click(ui.organizationSelector.get()); + await user.click(byRole('option', { name: 'org-1' }).get()); + expect(ui.monorepoProjectTitle.query()).not.toBeInTheDocument(); - await selectEvent.select(await ui.repositorySelector.find(), 'Github repo 1'); + await user.click(ui.repositorySelector.get()); + await user.click(byRole('option', { name: 'Github repo 1' }).get()); expect(await ui.notBoundRepositoryMessage.find()).toBeInTheDocument(); }); it('should display that selected repository is already bound to an existing project', async () => { + const user = userEvent.setup(); projectManagementHandler.setProjects([ mockProject({ key: 'key123', @@ -155,12 +157,13 @@ describe('github monorepo project setup', () => { expect(await ui.dopSettingSelector.find()).toBeInTheDocument(); expect(ui.monorepoProjectTitle.query()).not.toBeInTheDocument(); - await waitFor(async () => { - await selectEvent.select(await ui.organizationSelector.find(), 'org-1'); - }); + await user.click(ui.organizationSelector.get()); + await user.click(byRole('option', { name: 'org-1' }).get()); + expect(ui.monorepoProjectTitle.query()).not.toBeInTheDocument(); - await selectEvent.select(await ui.repositorySelector.find(), 'Github repo 1'); + await user.click(ui.repositorySelector.get()); + await user.click(byRole('option', { name: 'Github repo 1' }).get()); expect(await ui.alreadyBoundRepositoryMessage.find()).toBeInTheDocument(); expect(byRole('link', { name: 'Project GitHub 1' }).get()).toBeInTheDocument(); @@ -175,12 +178,14 @@ describe('github monorepo project setup', () => { expect(await ui.dopSettingSelector.find()).toBeInTheDocument(); expect(ui.monorepoProjectTitle.query()).not.toBeInTheDocument(); - await waitFor(async () => { - await selectEvent.select(await ui.organizationSelector.find(), 'org-1'); - }); + await user.click(ui.organizationSelector.get()); + await user.click(byRole('option', { name: 'org-1' }).get()); + expect(ui.monorepoProjectTitle.query()).not.toBeInTheDocument(); - await selectEvent.select(await ui.repositorySelector.find(), 'Github repo 1'); + await user.click(ui.repositorySelector.get()); + await user.click(byRole('option', { name: 'Github repo 1' }).get()); + expect(await ui.monorepoProjectTitle.find()).toBeInTheDocument(); let projects = byRole('textbox', { name: /onboarding.create_project.project_key/, diff --git a/server/sonar-web/src/main/js/apps/create/project/components/DopSettingDropdown.tsx b/server/sonar-web/src/main/js/apps/create/project/components/DopSettingDropdown.tsx index 599318c0894..38a182477aa 100644 --- a/server/sonar-web/src/main/js/apps/create/project/components/DopSettingDropdown.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/components/DopSettingDropdown.tsx @@ -37,6 +37,10 @@ export interface DopSettingDropdownProps { const MIN_SIZE_INSTANCES = 2; function optionRenderer(props: OptionProps<LabelValueSelectOption<DopSetting>, false>) { + // For tests and a11y + props.innerProps.role = 'option'; + props.innerProps['aria-selected'] = props.isSelected; + return <components.Option {...props}>{customOptions(props.data.value)}</components.Option>; } diff --git a/server/sonar-web/src/main/js/apps/permissions/test-utils.ts b/server/sonar-web/src/main/js/apps/permissions/test-utils.ts index f39396bd8cb..2e2eb11c4ee 100644 --- a/server/sonar-web/src/main/js/apps/permissions/test-utils.ts +++ b/server/sonar-web/src/main/js/apps/permissions/test-utils.ts @@ -19,7 +19,6 @@ */ import { waitFor } from '@testing-library/react'; import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup'; -import selectEvent from 'react-select-event'; import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import { Visibility } from '~sonar-aligned/types/component'; import { Permissions } from '../../types/permissions'; @@ -95,7 +94,9 @@ export function getPageObject(user: UserEvent) { await user.click(ui.closeModalBtn.get()); }, async chooseTemplate(name: string) { - await selectEvent.select(ui.templateSelect.get(), [name]); + await user.click(ui.templateSelect.get()); + await user.click(byRole('option', { name }).get()); + await user.click(ui.confirmApplyTemplateBtn.get()); }, async toggleFilterByPermission(permission: Permissions) { diff --git a/server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/ProjectBadges-test.tsx b/server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/ProjectBadges-test.tsx index 554a9cf5817..1ff688da6f8 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/ProjectBadges-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/badges/__tests__/ProjectBadges-test.tsx @@ -17,10 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { fireEvent, screen, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; -import selectEvent from 'react-select-event'; import { ComponentQualifier } from '~sonar-aligned/types/component'; import { MetricKey } from '~sonar-aligned/types/metrics'; import { getProjectBadgesToken } from '../../../../api/project-badges'; @@ -28,6 +27,7 @@ import { mockBranch } from '../../../../helpers/mocks/branch-like'; import { mockComponent } from '../../../../helpers/mocks/component'; import { renderComponent } from '../../../../helpers/testReactTestingUtils'; import { Location } from '../../../../helpers/urls'; +import { byRole } from '../../../../sonar-aligned/helpers/testSelector'; import ProjectBadges, { ProjectBadgesProps } from '../ProjectBadges'; import { BadgeType } from '../utils'; @@ -84,6 +84,7 @@ it('should renew token', async () => { }); it('should update params', async () => { + const user = userEvent.setup(); renderProjectBadges(); await appLoaded(); @@ -93,10 +94,7 @@ it('should update params', async () => { ), ).toBeInTheDocument(); - await selectEvent.select( - screen.getByLabelText('overview.badges.format'), - 'overview.badges.options.formats.url', - ); + await user.click(byRole('radio', { name: 'overview.badges.options.formats.url' }).get()); expect( screen.getByText( @@ -104,8 +102,8 @@ it('should update params', async () => { ), ).toBeInTheDocument(); - await selectEvent.openMenu(screen.getByLabelText('overview.badges.metric')); - fireEvent.click(screen.getByText(`metric.${MetricKey.coverage}.name`)); + await user.click(screen.getByLabelText('overview.badges.metric')); + await user.click(screen.getByText(`metric.${MetricKey.coverage}.name`)); expect( screen.getByText( @@ -113,7 +111,7 @@ it('should update params', async () => { ), ).toBeInTheDocument(); - fireEvent.click( + await user.click( screen.getByRole('button', { name: `overview.badges.${BadgeType.qualityGate}.alt overview.badges.${BadgeType.qualityGate}.description.${ComponentQualifier.Project}`, }), @@ -125,7 +123,7 @@ it('should update params', async () => { ), ).toBeInTheDocument(); - fireEvent.click( + await user.click( screen.getByRole('button', { name: `overview.badges.${BadgeType.measure}.alt overview.badges.${BadgeType.measure}.description.${ComponentQualifier.Project}`, }), @@ -139,11 +137,12 @@ it('should update params', async () => { }); it('should warn about deprecated metrics', async () => { + const user = userEvent.setup(); renderProjectBadges(); await appLoaded(); - await selectEvent.openMenu(screen.getByLabelText('overview.badges.metric')); - fireEvent.click(screen.getByText(`metric.${MetricKey.bugs}.name (deprecated)`)); + await user.click(screen.getByLabelText('overview.badges.metric')); + await user.click(screen.getByText(`metric.${MetricKey.bugs}.name (deprecated)`)); expect( screen.getByText( diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx index 972c76b4b12..6c5f3ea5c0b 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx +++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx @@ -52,6 +52,10 @@ export interface BranchOption { function renderBranchOption(props: OptionProps<BranchOption, false>) { const { data: option } = props; + // For tests and a11y + props.innerProps.role = 'option'; + props.innerProps['aria-selected'] = props.isSelected; + return ( <components.Option {...props}> {option.isInvalid ? ( diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx index 0e5888963cd..9ef1cf8280b 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx @@ -19,7 +19,6 @@ */ import userEvent from '@testing-library/user-event'; import { last } from 'lodash'; -import selectEvent from 'react-select-event'; import { byLabelText, byRole, byText } from '~sonar-aligned/helpers/testSelector'; import { MessageTypes } from '../../../../api/messages'; import BranchesServiceMock from '../../../../api/mocks/BranchesServiceMock'; @@ -429,13 +428,18 @@ function getPageObjects() { async function setReferenceBranchSetting(branch: string) { await user.click(ui.specificSettingRadio.get()); await user.click(ui.referenceBranchRadio.get()); - await selectEvent.select(ui.chooseBranchSelect.get(), branch); + + await user.click(ui.chooseBranchSelect.get()); + await user.click(byRole('option', { name: new RegExp(branch) }).get()); } async function setBranchReferenceToBranchSetting(branch: string, branchRef: string) { await openBranchSettingModal(branch); await user.click(last(ui.referenceBranchRadio.getAll()) as HTMLElement); - await selectEvent.select(ui.chooseBranchSelect.get(), branchRef); + + await user.click(ui.chooseBranchSelect.get()); + await user.click(byRole('option', { name: new RegExp(branchRef) }).get()); + await user.click(last(ui.saveButton.getAll()) as HTMLElement); } diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-it.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-it.tsx index 1daad90bad2..6be831f883b 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-it.tsx @@ -21,7 +21,6 @@ import { waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { addGlobalErrorMessage, addGlobalSuccessMessage } from 'design-system'; -import selectEvent from 'react-select-event'; import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import { QualityGatesServiceMock } from '../../../api/mocks/QualityGatesServiceMock'; import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization'; @@ -73,36 +72,45 @@ it('should require authorization if no permissions set', () => { }); it('should be able to select and save specific Quality Gate', async () => { + const user = userEvent.setup(); renderProjectQualityGateApp(); expect(await ui.qualityGateHeading.find()).toBeInTheDocument(); expect(ui.defaultRadioQualityGate.get()).toBeChecked(); - await userEvent.click(ui.specificRadioQualityGate.get()); + await user.click(ui.specificRadioQualityGate.get()); expect(ui.qualityGatesSelect.get()).toBeEnabled(); - await selectEvent.select(ui.qualityGatesSelect.get(), 'Sonar way'); - await userEvent.click(ui.saveButton.get()); + await user.click(ui.qualityGatesSelect.get()); + await user.click(byText('Sonar way').get()); + + await user.click(ui.saveButton.get()); expect(addGlobalSuccessMessage).toHaveBeenCalledWith('project_quality_gate.successfully_updated'); // Set back default QG - await userEvent.click(ui.defaultRadioQualityGate.get()); + await user.click(ui.defaultRadioQualityGate.get()); expect(ui.qualityGatesSelect.get()).toBeDisabled(); expect(ui.defaultRadioQualityGate.get()).toBeChecked(); - await userEvent.click(ui.saveButton.get()); + await user.click(ui.saveButton.get()); expect(addGlobalSuccessMessage).toHaveBeenCalledWith('project_quality_gate.successfully_updated'); }); it('shows warning for quality gate that doesnt have conditions on new code', async () => { + const user = userEvent.setup(); handler.setGetGateForProjectName('Sonar way'); renderProjectQualityGateApp(); - await userEvent.click(await ui.specificRadioQualityGate.find()); - await selectEvent.select(ui.qualityGatesSelect.get(), 'QG without conditions'); + await user.click(await ui.specificRadioQualityGate.find()); + + await user.click(ui.qualityGatesSelect.get()); + await user.click(byText('QG without conditions').get()); + expect(ui.QGWithoutConditionsOptionLabel.query()).not.toBeInTheDocument(); - await selectEvent.select(ui.qualityGatesSelect.get(), 'QG without new code conditions'); + await user.click(ui.qualityGatesSelect.get()); + await user.click(byText('QG without new code conditions').get()); + expect(ui.noConditionsNewCodeWarning.get()).toBeInTheDocument(); }); diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/projectQualityProfilesApp-it.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/projectQualityProfilesApp-it.tsx index ad7168e7f2b..7e67174767e 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/projectQualityProfilesApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/projectQualityProfilesApp-it.tsx @@ -19,7 +19,6 @@ */ import userEvent from '@testing-library/user-event'; import { addGlobalSuccessMessage } from 'design-system'; -import selectEvent from 'react-select-event'; import { byLabelText, byRole, byText } from '~sonar-aligned/helpers/testSelector'; import { ProfileProject, @@ -170,9 +169,14 @@ it('should be able to add and change profile for languages', async () => { expect(ui.selectProfile.get()).toBeDisabled(); expect(ui.buttonSave.get()).toBeInTheDocument(); - await selectEvent.select(ui.selectLanguage.get(), 'HTML'); + await user.click(ui.selectLanguage.get()); + await user.click(byRole('option', { name: 'HTML' }).get()); + expect(ui.selectProfile.get()).toBeEnabled(); - await selectEvent.select(ui.selectProfile.get(), 'html profile'); + + await user.click(ui.selectProfile.get()); + await user.click(byRole('option', { name: 'html profile' }).get()); + await user.click(ui.buttonSave.get()); expect(associateProject).toHaveBeenLastCalledWith( expect.objectContaining({ key: 'html', name: 'html profile' }), @@ -207,7 +211,9 @@ it('should be able to add and change profile for languages', async () => { expect(ui.newAnalysisWarningMessage.get()).toBeInTheDocument(); expect(ui.selectUseSpecificProfile.get()).toBeInTheDocument(); - await selectEvent.select(ui.selectUseSpecificProfile.get(), 'html default profile'); + await user.click(ui.selectUseSpecificProfile.get()); + await user.click(byRole('option', { name: 'html default profile' }).get()); + await user.click(ui.buttonSave.get()); expect(addGlobalSuccessMessage).toHaveBeenCalledWith( diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx index 582c48c0a3f..786bc5d2ee0 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx @@ -53,6 +53,10 @@ export default function LanguageProfileSelectOption(props: LanguageProfileSelect [option.label, option.language], ); + // For tests and a11y + props.innerProps.role = 'option'; + props.innerProps['aria-selected'] = props.isSelected; + return ( <components.Option {...props}> <div> diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx index a699ef58aad..4ef59d041e0 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx @@ -115,9 +115,15 @@ class Search extends React.PureComponent<Props, State> { handleVisibilityChange = ({ value }: LabelValueSelectOption) => this.props.onVisibilityChanged(value); - optionRenderer = (props: OptionProps<LabelValueSelectOption, false>) => ( - <components.Option {...props}>{this.renderQualifierOption(props.data)}</components.Option> - ); + optionRenderer = (props: OptionProps<LabelValueSelectOption, false>) => { + // For tests and a11y + props.innerProps.role = 'option'; + props.innerProps['aria-selected'] = props.isSelected; + + return ( + <components.Option {...props}>{this.renderQualifierOption(props.data)}</components.Option> + ); + }; singleValueRenderer = (props: SingleValueProps<LabelValueSelectOption, false>) => ( <components.SingleValue {...props}> diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx index b2599e0a883..4c7441c8b9b 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx @@ -19,7 +19,6 @@ */ import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import selectEvent from 'react-select-event'; import { byPlaceholderText, byRole, byText } from '~sonar-aligned/helpers/testSelector'; import { ComponentQualifier } from '~sonar-aligned/types/component'; import DopTranslationServiceMock from '../../../api/mocks/DopTranslationServiceMock'; @@ -76,6 +75,7 @@ jest.mock('../../../api/navigation', () => ({ })); const ui = { + pageDescription: byText('projects_management.page.description'), row: byRole('row'), firstProjectActions: byRole('button', { name: 'projects_management.show_actions_for_x.Project 1', @@ -193,12 +193,10 @@ it('should filter projects', async () => { const user = userEvent.setup(); renderProjectManagementApp(); await waitFor(() => expect(ui.row.getAll()).toHaveLength(5)); - // Should return Promise: (and same for all similar cases below) - // await waitFor(() => selectEvent.select(ui.visibilityFilter.get(), ‘visibility.public’)); - // Can be fixed by migrating ProjectManagementApp to functional component - await waitFor(() => { - selectEvent.select(ui.visibilityFilter.get(), 'visibility.public'); - }); + + await user.click(ui.visibilityFilter.get()); + await user.click(byRole('option', { name: 'visibility.public' }).get()); + await waitFor(() => expect(ui.row.getAll()).toHaveLength(4)); await user.click(ui.analysisDateFilter.get()); await user.click(await screen.findByRole('gridcell', { name: '5' })); @@ -206,15 +204,17 @@ it('should filter projects', async () => { await user.click(ui.provisionedFilter.get()); expect(ui.row.getAll()).toHaveLength(2); expect(ui.row.getAll()[1]).toHaveTextContent('Project 4'); - await waitFor(() => { - selectEvent.select(ui.qualifierFilter.get(), 'qualifiers.VW'); - }); + + await user.click(ui.qualifierFilter.get()); + await user.click(byRole('option', { name: 'qualifiers.VW' }).get()); + await waitFor(() => expect(ui.provisionedFilter.query()).not.toBeInTheDocument()); expect(ui.row.getAll()).toHaveLength(2); await waitFor(() => expect(ui.row.getAll()[1]).toHaveTextContent('Portfolio 1')); - await waitFor(() => { - selectEvent.select(ui.qualifierFilter.get(), 'qualifiers.APP'); - }); + + await user.click(ui.qualifierFilter.get()); + await user.click(byRole('option', { name: 'qualifiers.APP' }).get()); + expect(ui.provisionedFilter.query()).not.toBeInTheDocument(); expect(ui.row.getAll()).toHaveLength(2); await waitFor(() => expect(ui.row.getAll()[1]).toHaveTextContent('Application 1')); @@ -226,29 +226,35 @@ it('should search by text', async () => { await waitFor(() => expect(ui.row.getAll()).toHaveLength(5)); await user.type(ui.searchFilter.get(), 'provision'); expect(ui.row.getAll()).toHaveLength(2); - await waitFor(() => { - selectEvent.select(ui.qualifierFilter.get(), 'qualifiers.VW'); - }); + + await user.click(ui.qualifierFilter.get()); + await user.click(byRole('option', { name: 'qualifiers.VW' }).get()); + await waitFor(() => expect(ui.row.getAll()).toHaveLength(4)); expect(ui.searchFilter.get()).toHaveValue(''); await user.type(ui.searchFilter.get(), 'Portfolio 2'); expect(ui.row.getAll()).toHaveLength(2); - await waitFor(() => { - selectEvent.select(ui.qualifierFilter.get(), 'qualifiers.APP'); - }); + + await user.click(ui.qualifierFilter.get()); + await user.click(byRole('option', { name: 'qualifiers.APP' }).get()); + await waitFor(() => expect(ui.row.getAll()).toHaveLength(4)); expect(ui.searchFilter.get()).toHaveValue(''); await user.type(ui.searchFilter.get(), 'Application 3'); expect(ui.row.getAll()).toHaveLength(2); }); -it('should hide quilifier filter', () => { +it('should hide qualifier filter', async () => { renderProjectManagementApp({ qualifiers: [ComponentQualifier.Project] }); + // pretext to wait for loading + expect(await ui.pageDescription.find()).toBeInTheDocument(); expect(ui.qualifierFilter.query()).not.toBeInTheDocument(); }); -it('should hide create Project button', () => { +it('should hide create Project button', async () => { renderProjectManagementApp(); + // pretext to wait for loading + expect(await ui.pageDescription.find()).toBeInTheDocument(); expect(ui.createProject.query()).not.toBeInTheDocument(); }); @@ -313,10 +319,10 @@ describe('Bulk permission templates', () => { .byText('permission_templates.bulk_apply_permission_template.apply_to_selected.2') .get(), ).toBeInTheDocument(); - await selectEvent.select( - ui.bulkApplyDialog.by(ui.selectTemplate('required')).get(), - 'Permission Template 2', - ); + + await user.click(ui.bulkApplyDialog.by(ui.selectTemplate('required')).get()); + await user.click(byRole('option', { name: 'Permission Template 2' }).get()); + await user.click(ui.bulkApplyDialog.by(ui.apply).get()); expect( @@ -492,9 +498,9 @@ it('should load more and change the filter without caching old pages', async () expect(projectRows[1]).toHaveTextContent('Project 0'); expect(projectRows[60]).toHaveTextContent('Project 59'); - await waitFor(() => { - selectEvent.select(ui.qualifierFilter.get(), 'qualifiers.VW'); - }); + await user.click(ui.qualifierFilter.get()); + await user.click(byRole('option', { name: 'qualifiers.VW' }).get()); + await waitFor(() => expect(projectRows[1]).not.toBeInTheDocument()); const portfolioRows = ui.row.getAll(); expect(portfolioRows).toHaveLength(51); @@ -538,10 +544,10 @@ it('should apply template for single object', async () => { await user.click(ui.applyPermissionTemplate.get()); expect(ui.applyTemplateDialog.get()).toBeInTheDocument(); - await selectEvent.select( - ui.applyTemplateDialog.by(ui.selectTemplate('required')).get(), - 'Permission Template 2', - ); + + await user.click(ui.applyTemplateDialog.by(ui.selectTemplate('required')).get()); + await user.click(byRole('option', { name: 'Permission Template 2' }).get()); + await user.click(ui.applyTemplateDialog.by(ui.apply).get()); expect( @@ -642,16 +648,21 @@ it('should not apply permissions for github projects', async () => { }); it('should not show local badge for applications and portfolios', async () => { + const user = userEvent.setup(); dopTranslationHandler.gitHubConfigurations.push( mockGitHubConfiguration({ provisioningType: ProvisioningType.auto }), ); renderProjectManagementApp({}, {}, { featureList: [Feature.GithubProvisioning] }); await waitFor(() => expect(screen.getAllByText('local')).toHaveLength(3)); - await selectEvent.select(ui.qualifierFilter.get(), 'qualifiers.VW'); + await user.click(ui.qualifierFilter.get()); + await user.click(byRole('option', { name: 'qualifiers.VW' }).get()); + await waitFor(() => expect(screen.queryByText('local')).not.toBeInTheDocument()); - await selectEvent.select(ui.qualifierFilter.get(), 'qualifiers.APP'); + await user.click(ui.qualifierFilter.get()); + await user.click(byRole('option', { name: 'qualifiers.APP' }).get()); + expect(screen.queryByText('local')).not.toBeInTheDocument(); }); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx index 047c4106915..7bb41e0f127 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx @@ -19,7 +19,6 @@ */ import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import selectEvent from 'react-select-event'; import { byLabelText, byRole, byTestId } from '~sonar-aligned/helpers/testSelector'; import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock'; import UsersServiceMock from '../../../../api/mocks/UsersServiceMock'; @@ -614,7 +613,7 @@ it('should not allow to add prioritized_rule_issues condition if feature is not const dialog = byRole('dialog'); await user.click(dialog.byRole('radio', { name: 'quality_gates.conditions.overall_code' }).get()); - await selectEvent.openMenu(dialog.byRole('combobox').get()); + await user.click(dialog.byRole('combobox').get()); expect( byRole('option', { name: 'Issues from prioritized rules' }).query(), ).not.toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfileApp-it.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfileApp-it.tsx index 886c95b6fde..08e81e903aa 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfileApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfileApp-it.tsx @@ -19,7 +19,6 @@ */ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import selectEvent from 'react-select-event'; import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import QualityProfilesServiceMock from '../../../api/mocks/QualityProfilesServiceMock'; import { renderAppRoutes } from '../../../helpers/testReactTestingUtils'; @@ -109,7 +108,10 @@ describe('Admin or user with permission', () => { // Add user await user.click(ui.grantPermissionButton.get()); expect(ui.dialog.get()).toBeInTheDocument(); - await selectEvent.select(ui.selectUserOrGroup.get(), 'Buzz'); + + await user.click(ui.selectUserOrGroup.get()); + await user.click(byRole('option', { name: /^Buzz/ }).get()); + await user.click(ui.addButton.get()); expect(ui.permissionSection.byText('Buzz').get()).toBeInTheDocument(); @@ -134,7 +136,10 @@ describe('Admin or user with permission', () => { // Add Group await user.click(ui.grantPermissionButton.get()); expect(ui.dialog.get()).toBeInTheDocument(); - await selectEvent.select(ui.selectUserOrGroup.get(), 'ACDC'); + + await user.click(ui.selectUserOrGroup.get()); + await user.click(byRole('option', { name: /^ACDC/ }).get()); + await user.click(ui.addButton.get()); expect(ui.permissionSection.byText('ACDC').get()).toBeInTheDocument(); @@ -252,7 +257,10 @@ describe('Admin or user with permission', () => { await user.click(ui.changeParentButton.get()); expect(await ui.dialog.find()).toBeInTheDocument(); expect(ui.changeButton.get()).toBeDisabled(); - await selectEvent.select(ui.selectField.get(), 'PHP Sonar way 2'); + + await user.click(ui.selectField.get()); + await user.click(byRole('option', { name: 'PHP Sonar way 2' }).get()); + await user.click(ui.changeButton.get()); expect(ui.dialog.query()).not.toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfilesApp-it.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfilesApp-it.tsx index 3a2e6c88dd6..534b8c480b1 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfilesApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/QualityProfilesApp-it.tsx @@ -19,7 +19,6 @@ */ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import selectEvent from 'react-select-event'; import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import QualityProfilesServiceMock from '../../../api/mocks/QualityProfilesServiceMock'; import SettingsServiceMock from '../../../api/mocks/SettingsServiceMock'; @@ -136,7 +135,8 @@ it('should list Quality Profiles and filter by language', async () => { expect(await ui.listLinkCQualityProfile.find()).toBeInTheDocument(); expect(ui.listLinkJavaQualityProfile.get()).toBeInTheDocument(); - await selectEvent.select(ui.filterByLang.get(), 'C'); + await user.click(ui.filterByLang.get()); + await user.click(byRole('option', { name: 'C' }).get()); expect(ui.listLinkJavaQualityProfile.query()).not.toBeInTheDocument(); @@ -326,7 +326,9 @@ it('should be able to compare profiles', async () => { expect(ui.profileActions('java quality profile', 'Java').query()).not.toBeInTheDocument(); expect(ui.changelogLink.query()).not.toBeInTheDocument(); - await selectEvent.select(ui.compareDropdown.get(), 'java quality profile #2'); + await user.click(ui.compareDropdown.get()); + await user.click(byRole('option', { name: 'java quality profile #2' }).get()); + expect(await ui.comparisonDiffTableHeading(1, 'java quality profile').find()).toBeInTheDocument(); expect(ui.comparisonDiffTableHeading(1, 'java quality profile #2').get()).toBeInTheDocument(); expect(ui.comparisonModifiedTableHeading(1).get()).toBeInTheDocument(); @@ -344,7 +346,9 @@ it('should be able to activate or deactivate rules in comparison page', async () await user.click(await ui.listProfileActions('java quality profile #2', 'Java').find()); await user.click(ui.compareButton.get()); - await selectEvent.select(ui.compareDropdown.get(), 'java quality profile'); + + await user.click(ui.compareDropdown.get()); + await user.click(byRole('option', { name: 'java quality profile' }).get()); expect(await ui.summaryFewerRules(1).find()).toBeInTheDocument(); expect(ui.summaryAdditionalRules(1).get()).toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx index a157b450d1b..f4098305376 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx @@ -77,6 +77,10 @@ function OptionRenderer(props: Readonly<OptionProps<Option, false>>) { const { isDefault, label } = props.data; const intl = useIntl(); + // For tests and a11y + props.innerProps.role = 'option'; + props.innerProps['aria-selected'] = props.isSelected; + return ( <components.Option {...props}> <span>{label}</span> diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-it.tsx index 74dfe306819..34efb6e342c 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-it.tsx @@ -21,8 +21,7 @@ import { within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { Route } from 'react-router-dom'; -import selectEvent from 'react-select-event'; -import { byRole } from '~sonar-aligned/helpers/testSelector'; +import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock'; import { KeyboardKeys } from '../../../../helpers/keycodes'; import { mockComponent } from '../../../../helpers/mocks/component'; @@ -108,7 +107,9 @@ describe('Global Settings', () => { await user.click(await ui.categoryLink('property.category.languages').find()); expect(await ui.languagesHeading.find()).toBeInTheDocument(); - await selectEvent.select(ui.languagesSelect.get(), 'property.category.javascript'); + await user.click(ui.languagesSelect.get()); + await user.click(byText('property.category.javascript').get()); + expect(await ui.jsGeneralSubCategoryHeading.find()).toBeInTheDocument(); }); 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 index 22abab10298..03f1d690a21 100644 --- 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 @@ -19,7 +19,6 @@ */ import userEvent from '@testing-library/user-event'; import React from 'react'; -import selectEvent from 'react-select-event'; import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import AlmSettingsServiceMock from '../../../../../api/mocks/AlmSettingsServiceMock'; import CurrentUserContextProvider from '../../../../../app/components/current-user/CurrentUserContextProvider'; @@ -96,10 +95,8 @@ it.each([ const { rerender } = renderPRDecorationBinding(); expect(await ui.mainTitle.find()).toBeInTheDocument(); - // Set form data - await selectEvent.select(ui.input('name', 'combobox').get(), (content) => - content.includes(key), - ); + await user.click(ui.input('name', 'combobox').get()); + await user.click(byRole('option', { name: new RegExp(key) }).get()); const list = inputsList[alm]; for (const [inputId, value] of Object.entries(list)) { diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx index 2201fe6f32a..97c1404bf7c 100644 --- a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx @@ -20,7 +20,6 @@ import { screen, waitFor } 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 '~sonar-aligned/helpers/testSelector'; import ComponentsServiceMock from '../../../api/mocks/ComponentsServiceMock'; import DopTranslationServiceMock from '../../../api/mocks/DopTranslationServiceMock'; @@ -207,9 +206,9 @@ describe('different filters combinations', () => { await user.click(await ui.localFilter.find()); await waitFor(() => expect(ui.activityFilter.get()).toBeEnabled()); - await selectEvent.select( - ui.activityFilter.get(), - 'users.activity_filter.active_sonarlint_users', + await user.click(ui.activityFilter.get()); + await user.click( + byRole('option', { name: 'users.activity_filter.active_sonarlint_users' }).get(), ); expect(await ui.userRows.findAll()).toHaveLength(1); @@ -223,9 +222,9 @@ describe('different filters combinations', () => { await user.click(await ui.managedByScimFilter.find()); await waitFor(() => expect(ui.activityFilter.get()).toBeEnabled()); - await selectEvent.select( - ui.activityFilter.get(), - 'users.activity_filter.active_sonarqube_users', + await user.click(ui.activityFilter.get()); + await user.click( + byRole('option', { name: 'users.activity_filter.active_sonarqube_users' }).get(), ); expect(await ui.userRows.findAll()).toHaveLength(1); @@ -239,7 +238,8 @@ describe('different filters combinations', () => { await user.click(await ui.localAndManagedFilter.find()); await waitFor(() => expect(ui.activityFilter.get()).toBeEnabled()); - await selectEvent.select(ui.activityFilter.get(), 'users.activity_filter.inactive_users'); + await user.click(ui.activityFilter.get()); + await user.click(byRole('option', { name: 'users.activity_filter.inactive_users' }).get()); expect(await ui.userRows.findAll()).toHaveLength(2); expect(ui.evaRow.get()).toBeInTheDocument(); @@ -626,7 +626,9 @@ describe('in manage mode', () => { expect(getTokensList()).toHaveLength(2); // header + "No tokens" expect(await screen.findByText('users.no_tokens')).toBeInTheDocument(); - await selectEvent.select(ui.expiresInSelector.get(), 'users.tokens.expiration.0'); + await user.click(ui.expiresInSelector.get()); + await user.click(byRole('option', { name: 'users.tokens.expiration.0' }).get()); + await user.click(ui.generateButton.get()); expect(getTokensList()).toHaveLength(2); // header + "test" token expect(screen.queryByText('users.no_tokens')).not.toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx b/server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx index 761920d4b19..040d7ad7095 100644 --- a/server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx +++ b/server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx @@ -24,6 +24,10 @@ import { translate } from '../../helpers/l10n'; import { AlmInstanceBase } from '../../types/alm-settings'; function optionRenderer(props: OptionProps<LabelValueSelectOption<AlmInstanceBase>, false>) { + // For tests and a11y + props.innerProps.role = 'option'; + props.innerProps['aria-selected'] = props.isSelected; + return <components.Option {...props}>{customOptions(props.data.value)}</components.Option>; } diff --git a/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx index 0f3a5e8d3e2..5efadd4646c 100644 --- a/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx @@ -19,13 +19,13 @@ */ import userEvent from '@testing-library/user-event'; import React from 'react'; -import selectEvent from 'react-select-event'; import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; import UserTokensMock from '../../../../api/mocks/UserTokensMock'; import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; import { mockComponent } from '../../../../helpers/mocks/component'; import { mockLanguage, mockLoggedInUser } from '../../../../helpers/testMocks'; import { RenderContext, renderApp } from '../../../../helpers/testReactTestingUtils'; +import { byRole } from '../../../../sonar-aligned/helpers/testSelector'; import { AlmKeys } from '../../../../types/alm-settings'; import { Feature } from '../../../../types/features'; import { @@ -154,7 +154,10 @@ it('should generate/delete a new token or use existing one', async () => { // Revoke current token and create new one await user.click(ui.deleteTokenButton.get()); await user.type(ui.tokenNameInput.get(), 'newtoken'); - await selectEvent.select(ui.expiresInSelect.get(), 'users.tokens.expiration.365'); + + await user.click(ui.expiresInSelect.get()); + await user.click(byRole('option', { name: 'users.tokens.expiration.365' }).get()); + await user.click(ui.generateTokenButton.get()); expect(ui.tokenValue.get()).toBeInTheDocument(); await user.click(ui.continueButton.getAll()[0]); diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx index 06ff90c6eb6..e6553192e9f 100644 --- a/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx @@ -19,13 +19,13 @@ */ import userEvent from '@testing-library/user-event'; import React from 'react'; -import selectEvent from 'react-select-event'; import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; import UserTokensMock from '../../../../api/mocks/UserTokensMock'; import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; import { mockComponent } from '../../../../helpers/mocks/component'; import { mockLanguage, mockLoggedInUser } from '../../../../helpers/testMocks'; import { RenderContext, renderApp } from '../../../../helpers/testReactTestingUtils'; +import { byRole } from '../../../../sonar-aligned/helpers/testSelector'; import { AlmKeys } from '../../../../types/alm-settings'; import { Feature } from '../../../../types/features'; import { @@ -162,7 +162,10 @@ it('should generate/delete a new token or use existing one', async () => { // Revoke current token and create new one await user.click(ui.deleteTokenButton.get()); await user.type(ui.tokenNameInput.get(), 'newtoken'); - await selectEvent.select(ui.expiresInSelect.get(), 'users.tokens.expiration.365'); + + await user.click(ui.expiresInSelect.get()); + await user.click(byRole('option', { name: 'users.tokens.expiration.365' }).get()); + await user.click(ui.generateTokenButton.get()); expect(ui.tokenValue.get()).toBeInTheDocument(); await user.click(ui.continueButton.getAll()[0]); diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-it.tsx index fc5ac5c8a7e..6ac323f6d0c 100644 --- a/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-it.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-it.tsx @@ -19,11 +19,11 @@ */ import userEvent from '@testing-library/user-event'; import React from 'react'; -import selectEvent from 'react-select-event'; import UserTokensMock from '../../../../api/mocks/UserTokensMock'; import { mockComponent } from '../../../../helpers/mocks/component'; import { mockLanguage, mockLoggedInUser } from '../../../../helpers/testMocks'; import { RenderContext, renderApp } from '../../../../helpers/testReactTestingUtils'; +import { byRole } from '../../../../sonar-aligned/helpers/testSelector'; import { getCommonNodes, getCopyToClipboardValue, @@ -110,7 +110,9 @@ it('should generate/delete a new token or use existing one', async () => { // Revoke current token and create new one await user.click(ui.deleteTokenButton.get()); await user.type(ui.tokenNameInput.get(), 'newtoken'); - await selectEvent.select(ui.expiresInSelect.get(), 'users.tokens.expiration.365'); + await user.click(ui.expiresInSelect.get()); + await user.click(byRole('option', { name: 'users.tokens.expiration.365' }).get()); + await user.click(ui.generateTokenButton.get()); expect(ui.tokenValue.get()).toBeInTheDocument(); await user.click(ui.continueButton.getAll()[0]); diff --git a/server/sonar-web/src/main/js/components/tutorials/other/__tests__/OtherTutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/other/__tests__/OtherTutorial-it.tsx index dbc445d6326..f99d456c3c8 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/__tests__/OtherTutorial-it.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/other/__tests__/OtherTutorial-it.tsx @@ -19,7 +19,6 @@ */ import userEvent from '@testing-library/user-event'; import React from 'react'; -import selectEvent from 'react-select-event'; import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import UserTokensMock from '../../../../api/mocks/UserTokensMock'; import { mockComponent } from '../../../../helpers/mocks/component'; @@ -65,7 +64,9 @@ it('should generate/delete a new token or use existing one', async () => { // Generating token await user.type(ui.tokenNameInput.get(), 'Testing token'); - await selectEvent.select(ui.expiresInSelect.get(), 'users.tokens.expiration.365'); + await user.click(ui.expiresInSelect.get()); + await user.click(byRole('option', { name: 'users.tokens.expiration.365' }).get()); + await user.click(ui.generateTokenButton.get()); expect(ui.continueButton.get()).toBeEnabled(); diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock index ca0baf45222..8983d84e8b1 100644 --- a/server/sonar-web/yarn.lock +++ b/server/sonar-web/yarn.lock @@ -2084,7 +2084,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3": version: 7.16.7 resolution: "@babel/runtime@npm:7.16.7" dependencies: @@ -5322,72 +5322,24 @@ __metadata: languageName: node linkType: hard -"@testing-library/dom@npm:9.3.4, @testing-library/dom@npm:>=7, @testing-library/dom@npm:^9.0.0": - version: 9.3.4 - resolution: "@testing-library/dom@npm:9.3.4" - dependencies: - "@babel/code-frame": "npm:^7.10.4" - "@babel/runtime": "npm:^7.12.5" - "@types/aria-query": "npm:^5.0.1" - aria-query: "npm:5.1.3" - chalk: "npm:^4.1.0" - dom-accessibility-api: "npm:^0.5.9" - lz-string: "npm:^1.5.0" - pretty-format: "npm:^27.0.2" - checksum: 10/510da752ea76f4a10a0a4e3a77917b0302cf03effe576cd3534cab7e796533ee2b0e9fb6fb11b911a1ebd7c70a0bb6f235bf4f816c9b82b95b8fe0cddfd10975 - languageName: node - linkType: hard - -"@testing-library/jest-dom@npm:6.4.6": - version: 6.4.6 - resolution: "@testing-library/jest-dom@npm:6.4.6" +"@testing-library/jest-dom@npm:6.5.0": + version: 6.5.0 + resolution: "@testing-library/jest-dom@npm:6.5.0" dependencies: "@adobe/css-tools": "npm:^4.4.0" - "@babel/runtime": "npm:^7.9.2" aria-query: "npm:^5.0.0" chalk: "npm:^3.0.0" css.escape: "npm:^1.5.1" dom-accessibility-api: "npm:^0.6.3" lodash: "npm:^4.17.21" redent: "npm:^3.0.0" - peerDependencies: - "@jest/globals": ">= 28" - "@types/bun": "*" - "@types/jest": ">= 28" - jest: ">= 28" - vitest: ">= 0.32" - peerDependenciesMeta: - "@jest/globals": - optional: true - "@types/bun": - optional: true - "@types/jest": - optional: true - jest: - optional: true - vitest: - optional: true - checksum: 10/94fad29d740ff2c34967c644e2481a472aa8eeb1f11cdec5d4f81f14b2576660387551264c0fa718c15bfc61dd342f7621d888fe3e4ba1b7f830fe65bdd37bc8 - languageName: node - linkType: hard - -"@testing-library/react@npm:14.2.1": - version: 14.2.1 - resolution: "@testing-library/react@npm:14.2.1" - dependencies: - "@babel/runtime": "npm:^7.12.5" - "@testing-library/dom": "npm:^9.0.0" - "@types/react-dom": "npm:^18.0.0" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/e02b2f32ae79665a79fc4d8ee053fd3832bfcd4753aa1dba05cdece1a9f59c72a0fae91e0a9387597dcb686d631a722729f2878e38dc95e6f23b291ad8d09b6c + checksum: 10/3d2080888af5fd7306f57448beb5a23f55d965e265b5e53394fffc112dfb0678d616a5274ff0200c46c7618f293520f86fc8562eecd8bdbc0dbb3294d63ec431 languageName: node linkType: hard -"@testing-library/react@npm:16.0.0": - version: 16.0.0 - resolution: "@testing-library/react@npm:16.0.0" +"@testing-library/react@npm:16.0.1": + version: 16.0.1 + resolution: "@testing-library/react@npm:16.0.1" dependencies: "@babel/runtime": "npm:^7.12.5" peerDependencies: @@ -5401,7 +5353,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/b32894be94e31276138decfa6bcea69dfebc0c37cf91499ff6c878f41eb1154a43a7df6eb1e72e7bede78468af6cb67ca59e4acd3206b41f3ecdae2c6efdf67e + checksum: 10/904b48881cf5bd208e25899e168f5c99c78ed6d77389544838d9d861a038d2c5c5385863ee9a367436770cbf7d21c5e05a991b9e24a33806e9ac985df2448185 languageName: node linkType: hard @@ -5841,15 +5793,6 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.0.0": - version: 18.2.17 - resolution: "@types/react-dom@npm:18.2.17" - dependencies: - "@types/react": "npm:*" - checksum: 10/fe0dbb3224b48515da8fe25559e3777d756a27c3f22903f0b1b020de8d68bd57eb1f0af62b52ee65d9632637950afed8cbad24d158c4f3d910d083d49bd73fba - languageName: node - linkType: hard - "@types/react-helmet@npm:6.1.11": version: 6.1.11 resolution: "@types/react-helmet@npm:6.1.11" @@ -6448,9 +6391,9 @@ __metadata: "@swc/core": "npm:1.6.6" "@swc/jest": "npm:0.2.36" "@tanstack/react-query": "npm:5.18.1" - "@testing-library/dom": "npm:9.3.4" - "@testing-library/jest-dom": "npm:6.4.6" - "@testing-library/react": "npm:14.2.1" + "@testing-library/dom": "npm:10.2.0" + "@testing-library/jest-dom": "npm:6.5.0" + "@testing-library/react": "npm:16.0.1" "@testing-library/user-event": "npm:14.5.2" "@types/cheerio": "npm:0.22.35" "@types/classnames": "npm:2.3.1" @@ -6539,7 +6482,6 @@ __metadata: react-modal: "npm:3.16.1" react-router-dom: "npm:6.24.0" react-select: "npm:5.7.7" - react-select-event: "npm:5.5.1" react-virtualized: "npm:9.22.5" regenerator-runtime: "npm:0.14.1" shared-store-hook: "npm:0.0.4" @@ -6818,15 +6760,6 @@ __metadata: languageName: node linkType: hard -"aria-query@npm:5.1.3, aria-query@npm:~5.1.3": - version: 5.1.3 - resolution: "aria-query@npm:5.1.3" - dependencies: - deep-equal: "npm:^2.0.5" - checksum: 10/e5da608a7c4954bfece2d879342b6c218b6b207e2d9e5af270b5e38ef8418f02d122afdc948b68e32649b849a38377785252059090d66fa8081da95d1609c0d2 - languageName: node - linkType: hard - "aria-query@npm:5.3.0": version: 5.3.0 resolution: "aria-query@npm:5.3.0" @@ -6843,6 +6776,15 @@ __metadata: languageName: node linkType: hard +"aria-query@npm:~5.1.3": + version: 5.1.3 + resolution: "aria-query@npm:5.1.3" + dependencies: + deep-equal: "npm:^2.0.5" + checksum: 10/e5da608a7c4954bfece2d879342b6c218b6b207e2d9e5af270b5e38ef8418f02d122afdc948b68e32649b849a38377785252059090d66fa8081da95d1609c0d2 + languageName: node + linkType: hard + "array-buffer-byte-length@npm:^1.0.0": version: 1.0.0 resolution: "array-buffer-byte-length@npm:1.0.0" @@ -8563,8 +8505,8 @@ __metadata: "@emotion/babel-plugin-jsx-pragmatic": "npm:0.2.1" "@sonarsource/echoes-react": "npm:0.6.0" "@testing-library/dom": "npm:10.2.0" - "@testing-library/jest-dom": "npm:6.4.6" - "@testing-library/react": "npm:16.0.0" + "@testing-library/jest-dom": "npm:6.5.0" + "@testing-library/react": "npm:16.0.1" "@testing-library/user-event": "npm:14.5.2" "@types/d3-array": "npm:3.2.1" "@types/d3-hierarchy": "npm:~3.1.7" @@ -14824,15 +14766,6 @@ __metadata: languageName: node linkType: hard -"react-select-event@npm:5.5.1": - version: 5.5.1 - resolution: "react-select-event@npm:5.5.1" - dependencies: - "@testing-library/dom": "npm:>=7" - checksum: 10/b0917707ff9aa6eb887178107879403e483aeaacb14f2d74fef25a805538299be88adfc87709decc1b9f9280679a5ddf0d2c6f92178b7bbd416ea7c7a9149757 - languageName: node - linkType: hard - "react-select@npm:5.7.7": version: 5.7.7 resolution: "react-select@npm:5.7.7" |