@@ -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; | |||
}, | |||
}); |
@@ -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>; | |||
} | |||
} |
@@ -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() { |
@@ -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']> = {}) { |
@@ -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(); | |||
}); | |||
@@ -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']> = {}) { |
@@ -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' }), |
@@ -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); |