Browse Source

SONAR-21692 Remove act warnings from Toggler, IssuesSidebar, IssuesAppGuides, MultiSelectMenu, DropdownMenu

tags/10.5.0.89998
stanislavh 2 months ago
parent
commit
b9fdc3abf6

+ 34
- 1
server/sonar-web/design-system/config/jest/SetupReactTestingLibrary.ts View File

@@ -18,10 +18,43 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import '@testing-library/jest-dom';
import { configure } from '@testing-library/react';
import { configure, fireEvent, screen, waitFor } from '@testing-library/react';
import React from 'react';

configure({
asyncUtilTimeout: 3000,
});
global.React = React;

expect.extend({
async toHaveATooltipWithContent(received: any, content: string) {
if (!(received instanceof Element)) {
return {
pass: false,
message: () => `Received object is not an HTMLElement, and cannot have a tooltip`,
};
}

fireEvent.pointerEnter(received);
const tooltip = await screen.findByRole('tooltip');

const result = tooltip.textContent?.includes(content)
? {
pass: true,
message: () => `Tooltip content "${tooltip.textContent}" contains expected "${content}"`,
}
: {
pass: false,
message: () =>
`Tooltip content "${tooltip.textContent}" does not contain expected "${content}"`,
};

fireEvent.pointerLeave(received);

await waitFor(() => {
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
});

return result;
},
});

+ 26
- 0
server/sonar-web/design-system/src/@types/jest.d.ts View File

@@ -0,0 +1,26 @@
/*
* SonarQube
* Copyright (C) 2009-2024 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.
*/

declare namespace jest {
interface Matchers<R> {
toHaveATooltipWithContent(content: string): Promise<CustomMatcherResult>;
toHaveNoA11yViolations(): Promise<CustomMatcherResult>;
}
}

+ 3
- 29
server/sonar-web/design-system/src/components/__tests__/DropdownMenu-test.tsx View File

@@ -17,7 +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 { act, screen, waitFor } from '@testing-library/react';
import { screen } from '@testing-library/react';
import { noop } from 'lodash';
import { render, renderWithRouter } from '../../helpers/testUtils';
import {
@@ -35,15 +35,6 @@ import {
import { Tooltip } from '../Tooltip';
import { MenuIcon } from '../icons/MenuIcon';

beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
});

it('should render a full menu correctly', () => {
renderDropdownMenu();
expect(screen.getByRole('menuitem', { name: 'My header' })).toBeInTheDocument();
@@ -52,7 +43,7 @@ it('should render a full menu correctly', () => {
});

it('menu items should work with tooltips', async () => {
const { user } = render(
render(
<Tooltip overlay="test tooltip">
<ItemButton onClick={jest.fn()}>button</ItemButton>
</Tooltip>,
@@ -61,24 +52,7 @@ it('menu items should work with tooltips', async () => {
);

expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();

await user.hover(screen.getByRole('menuitem'));
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();

act(() => {
jest.runAllTimers();
});
expect(screen.getByRole('tooltip')).toBeVisible();

await user.unhover(screen.getByRole('menuitem'));
expect(screen.getByRole('tooltip')).toBeVisible();

act(() => {
jest.runAllTimers();
});
await waitFor(() => {
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
});
await expect(screen.getByRole('menuitem')).toHaveATooltipWithContent('test tooltip');
});

function renderDropdownMenu() {

+ 2
- 12
server/sonar-web/design-system/src/components/input/__tests__/MultiSelectMenu-test.tsx View File

@@ -24,15 +24,6 @@ import { MultiSelectMenu } from '../MultiSelectMenu';

const elements = ['foo', 'bar', 'baz'];

beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
});

it('should allow selecting and deselecting a new option', async () => {
const user = userEvent.setup({ delay: null });
const onSelect = jest.fn();
@@ -40,7 +31,6 @@ it('should allow selecting and deselecting a new option', async () => {
renderMultiselect({ elements, onSelect, onUnselect, allowNewElements: true });

await user.keyboard('new option');
jest.runAllTimers(); // skip the debounce

expect(await screen.findByText('new option')).toBeInTheDocument();

@@ -89,9 +79,9 @@ it('should ignore the left and right arrow keys', async () => {
expect(onSelect).toHaveBeenCalledWith('baz');
});

it('should show no results', () => {
it('should show no results', async () => {
renderMultiselect();
expect(screen.getByText('no results')).toBeInTheDocument();
expect(await screen.findByText('no results')).toBeInTheDocument();
});

function renderMultiselect(props: Partial<MultiSelectMenu['props']> = {}) {

+ 3
- 2
server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppGuides-it.tsx View File

@@ -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 { waitForElementToBeRemoved } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { mockCurrentUser } from '../../../helpers/testMocks';
@@ -167,11 +168,11 @@ describe('issue guides', () => {
expect(ui.guidePopup.query()).not.toBeInTheDocument();
});

it('should not show guide if there are no issues', () => {
it('should not show guide if there are no issues', async () => {
issuesHandler.setIssueList([]);
renderIssueApp(mockCurrentUser({ isLoggedIn: true }));

expect(ui.loading.query()).not.toBeInTheDocument();
await waitForElementToBeRemoved(ui.loading.query());
expect(ui.guidePopup.query()).not.toBeInTheDocument();
});


+ 5
- 10
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx View File

@@ -17,15 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { screen } from '@testing-library/react';
import { screen, waitFor } from '@testing-library/react';
import * as React from 'react';
import { mockComponent } from '../../../../helpers/mocks/component';
import { mockQuery } from '../../../../helpers/mocks/issues';
import {
renderOwaspTop102021Category,
renderOwaspTop10Category,
renderSonarSourceSecurityCategory,
} from '../../../../helpers/security-standard';
import { mockAppState } from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { ComponentQualifier } from '../../../../types/component';
@@ -123,7 +118,7 @@ it.each([
expect(screen.getByText(text)).toBeInTheDocument();
});

it('should call functions from security-standard', () => {
it('should render correctly for standards', async () => {
renderSidebar({
component: mockComponent({ qualifier: ComponentQualifier.Application }),
query: {
@@ -134,9 +129,9 @@ it('should call functions from security-standard', () => {
},
});

expect(renderOwaspTop10Category).toHaveBeenCalledTimes(1);
expect(renderOwaspTop102021Category).toHaveBeenCalledTimes(1);
expect(renderSonarSourceSecurityCategory).toHaveBeenCalledTimes(1);
await waitFor(() => {
expect(screen.getByLabelText('x_selected.3')).toBeInTheDocument();
});
});

function renderSidebar(props: Partial<Sidebar['props']> = {}) {

+ 1
- 1
server/sonar-web/src/main/js/apps/issues/test-utils.tsx View File

@@ -45,7 +45,7 @@ export const componentsHandler = new ComponentsServiceMock();
export const branchHandler = new BranchesServiceMock();

export const ui = {
loading: byText('loading'),
loading: byText('issues.loading_issues'),
issuePageHeadering: byRole('heading', { level: 1, name: 'issues.page' }),
issueItemAction1: byRole('link', { name: 'Issue with no location message' }),
issueItemAction2: byRole('link', { name: 'FlowIssue' }),

+ 6
- 20
server/sonar-web/src/main/js/components/controls/__tests__/Toggler-test.tsx View File

@@ -17,21 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { act, fireEvent, render } from '@testing-library/react';
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
import * as React from 'react';
import { byRole } from '../../../helpers/testSelector';
import Toggler from '../Toggler';

beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
});
const ui = {
toggleButton: byRole('button', { name: 'toggle' }),
outButton: byRole('button', { name: 'out' }),
@@ -43,13 +35,11 @@ const ui = {

async function openToggler(user: UserEvent) {
await user.click(ui.toggleButton.get());
jest.runAllTimers();
expect(ui.overlayButton.get()).toBeInTheDocument();
}

function focusOut() {
fireEvent.focus(ui.overlayButton.get());
fireEvent.focus(ui.outButton.get());
async function focusOut() {
await userEvent.click(ui.outButton.get());
}

it('should handle key up/down', async () => {
@@ -126,13 +116,13 @@ it('should handle focus correctly', async () => {

await openToggler(user);

focusOut();
await focusOut();
expect(ui.overlayButton.query()).not.toBeInTheDocument();

rerender({ closeOnFocusOut: false });
await openToggler(user);

focusOut();
await focusOut();
expect(ui.overlayButton.get()).toBeInTheDocument();
});

@@ -197,11 +187,7 @@ it('should open/close correctly when default props is applied', async () => {
expect(await ui.overlayButton.find()).toBeInTheDocument();

// Focus out should close
// I have no idea why only act + this 2 lines work, but fireEvent.focus does not
act(() => {
ui.overlayButton.get().focus();
ui.outButton.get().focus();
});
await focusOut();
expect(ui.overlayButton.query()).not.toBeInTheDocument();

await openToggler(user);

Loading…
Cancel
Save