Browse Source

SONAR-18421 Migrate project nav tests to RTL

tags/10.0.0.68432
Jeremy Davis 1 year ago
parent
commit
f3b29dddf6
24 changed files with 294 additions and 1174 deletions
  1. 1
    3
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx
  2. 24
    9
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavProjectBindingErrorNotif-test.tsx
  3. 0
    44
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavProjectBindingErrorNotif-test.tsx.snap
  4. 2
    2
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/BadgeParams.tsx
  5. 0
    49
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/BadgeButton-test.tsx
  6. 0
    72
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/BadgeParams-test.tsx
  7. 79
    38
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/ProjectBadges-test.tsx
  8. 0
    39
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap
  9. 0
    87
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/BadgeParams-test.tsx.snap
  10. 0
    62
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/ProjectBadges-test.tsx.snap
  11. 8
    10
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaLinks.tsx
  12. 29
    42
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaTags.tsx
  13. 0
    72
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaLink-test.tsx
  14. 27
    14
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaQualityProfiles-test.tsx
  15. 0
    46
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaSize-test.tsx
  16. 51
    16
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaTags-test.tsx
  17. 0
    57
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaTagsSelector-test.tsx
  18. 0
    87
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaLink-test.tsx.snap
  19. 0
    130
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaQualityProfiles-test.tsx.snap
  20. 0
    102
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaSize-test.tsx.snap
  21. 0
    55
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap
  22. 62
    62
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/notifications/__tests__/ProjectNotifications-test.tsx
  23. 0
    76
      server/sonar-web/src/main/js/app/components/nav/component/projectInformation/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap
  24. 11
    0
      server/sonar-web/src/main/js/helpers/testMocks.ts

+ 1
- 3
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx View File

@@ -30,7 +30,7 @@ export interface ComponentNavProjectBindingErrorNotifProps {
component: Component;
}

export function ComponentNavProjectBindingErrorNotif(
export default function ComponentNavProjectBindingErrorNotif(
props: ComponentNavProjectBindingErrorNotifProps
) {
const { component } = props;
@@ -56,5 +56,3 @@ export function ComponentNavProjectBindingErrorNotif(
</Alert>
);
}

export default ComponentNavProjectBindingErrorNotif;

+ 24
- 9
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavProjectBindingErrorNotif-test.tsx View File

@@ -17,23 +17,38 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import { screen } from '@testing-library/react';
import * as React from 'react';
import { mockComponent } from '../../../../../helpers/mocks/component';
import {
ComponentNavProjectBindingErrorNotif,
import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
import ComponentNavProjectBindingErrorNotif, {
ComponentNavProjectBindingErrorNotifProps,
} from '../ComponentNavProjectBindingErrorNotif';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('non-project admin');
it('should not show a link if use is not allowed', () => {
renderComponentNavProjectBindingErrorNotif({
component: mockComponent({ configuration: { showSettings: false } }),
});
expect(
shallowRender({ component: mockComponent({ configuration: { showSettings: true } }) })
).toMatchSnapshot('project admin');
screen.queryByRole('link', {
name: 'component_navigation.pr_deco.action.check_project_settings',
})
).not.toBeInTheDocument();
});

function shallowRender(props: Partial<ComponentNavProjectBindingErrorNotifProps> = {}) {
return shallow<ComponentNavProjectBindingErrorNotifProps>(
it('should show a link if use is allowed', () => {
renderComponentNavProjectBindingErrorNotif({
component: mockComponent({ configuration: { showSettings: true } }),
});
expect(
screen.getByRole('link', { name: 'component_navigation.pr_deco.action.check_project_settings' })
).toBeInTheDocument();
});

function renderComponentNavProjectBindingErrorNotif(
props: Partial<ComponentNavProjectBindingErrorNotifProps> = {}
) {
return renderComponent(
<ComponentNavProjectBindingErrorNotif component={mockComponent()} {...props} />
);
}

+ 0
- 44
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavProjectBindingErrorNotif-test.tsx.snap View File

@@ -1,44 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: non-project admin 1`] = `
<Alert
display="banner"
variant="warning"
>
<FormattedMessage
defaultMessage="component_navigation.pr_deco.error_detected_X"
id="component_navigation.pr_deco.error_detected_X"
values={
{
"action": "component_navigation.pr_deco.action.contact_project_admin",
}
}
/>
</Alert>
`;

exports[`should render correctly: project admin 1`] = `
<Alert
display="banner"
variant="warning"
>
<FormattedMessage
defaultMessage="component_navigation.pr_deco.error_detected_X"
id="component_navigation.pr_deco.error_detected_X"
values={
{
"action": <ForwardRef(Link)
to={
{
"pathname": "/project/settings",
"search": "?id=my-project&category=pull_request_decoration_binding",
}
}
>
component_navigation.pr_deco.action.check_project_settings
</ForwardRef(Link)>,
}
}
/>
</Alert>
`;

+ 2
- 2
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/BadgeParams.tsx View File

@@ -110,7 +110,7 @@ export class BadgeParams extends React.PureComponent<Props> {
</label>
<Select
className="input-medium it__metric-badge-select"
name="badge-metric"
inputId="badge-metric"
isSearchable={false}
onChange={this.handleMetricChange}
options={metricOptions}
@@ -139,7 +139,7 @@ export class BadgeParams extends React.PureComponent<Props> {
</label>
<Select
className="input-medium"
name="badge-format"
inputId="badge-format"
isSearchable={false}
onChange={this.handleFormatChange}
options={formatOptions}

+ 0
- 49
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/BadgeButton-test.tsx View File

@@ -1,49 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { click } from '../../../../../../../helpers/testUtils';
import BadgeButton from '../BadgeButton';
import { BadgeType } from '../utils';

it('should display correctly', () => {
expect(getWrapper()).toMatchSnapshot();
expect(getWrapper({ selected: true })).toMatchSnapshot();
expect(getWrapper({ type: BadgeType.measure })).toMatchSnapshot();
});

it('should return the badge type on click', () => {
const onClick = jest.fn();
const wrapper = getWrapper({ onClick });
click(wrapper.find('Button'));
expect(onClick).toHaveBeenCalledWith(BadgeType.qualityGate);
});

function getWrapper(props = {}) {
return shallow(
<BadgeButton
onClick={jest.fn()}
selected={false}
type={BadgeType.qualityGate}
url="http://foo.bar"
{...props}
/>
);
}

+ 0
- 72
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/BadgeParams-test.tsx View File

@@ -1,72 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { Metric } from '../../../../../../../types/types';
import { BadgeParams } from '../BadgeParams';
import { BadgeType } from '../utils';

jest.mock('../../../../../../../api/web-api', () => ({
fetchWebApi: () =>
Promise.resolve([
{
path: 'api/project_badges',
actions: [
{
key: 'measure',
params: [{ key: 'metric', possibleValues: ['alert_status', 'coverage'] }],
},
],
},
]),
}));

const METRICS = {
alert_status: { key: 'alert_status', name: 'Quality Gate' } as Metric,
coverage: { key: 'coverage', name: 'Coverage' } as Metric,
};

it('should display measure badge params', () => {
const updateOptions = jest.fn();
const wrapper = getWrapper({ updateOptions, type: BadgeType.measure });
expect(wrapper).toMatchSnapshot();
(wrapper.instance() as BadgeParams).handleMetricChange({ value: 'code_smell' });
expect(updateOptions).toHaveBeenCalledWith({ metric: 'code_smell' });
});

it('should display quality gate badge params', () => {
const updateOptions = jest.fn();
const wrapper = getWrapper({ updateOptions, type: BadgeType.qualityGate });
expect(wrapper).toMatchSnapshot();
(wrapper.instance() as BadgeParams).handleFormatChange({ value: 'md' });
expect(updateOptions).toHaveBeenCalledWith({ format: 'md' });
});

function getWrapper(props = {}) {
return shallow(
<BadgeParams
metrics={METRICS}
options={{ metric: 'alert_status' }}
type={BadgeType.measure}
updateOptions={jest.fn()}
{...props}
/>
);
}

+ 79
- 38
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/ProjectBadges-test.tsx View File

@@ -17,17 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
import selectEvent from 'react-select-event';
import { getProjectBadgesToken } from '../../../../../../../api/project-badges';
import CodeSnippet from '../../../../../../../components/common/CodeSnippet';
import { mockBranch } from '../../../../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../../../../helpers/mocks/component';
import { waitAndUpdate } from '../../../../../../../helpers/testUtils';
import { renderComponent } from '../../../../../../../helpers/testReactTestingUtils';
import { Location } from '../../../../../../../helpers/urls';
import { ComponentQualifier } from '../../../../../../../types/component';
import BadgeButton from '../BadgeButton';
import { MetricKey } from '../../../../../../../types/metrics';
import ProjectBadges from '../ProjectBadges';
import { BadgeType } from '../utils';

jest.mock('../../../../../../../helpers/urls', () => ({
getHostUrl: () => 'host',
@@ -40,51 +42,90 @@ jest.mock('../../../../../../../api/project-badges', () => ({
renewProjectBadgesToken: jest.fn().mockResolvedValue({}),
}));

jest.mock('react', () => {
return {
...jest.requireActual('react'),
createRef: jest.fn().mockReturnValue({ current: document.createElement('h3') }),
};
});

it('should display correctly', async () => {
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});
jest.mock('../../../../../../../api/web-api', () => ({
fetchWebApi: () =>
Promise.resolve([
{
path: 'api/project_badges',
actions: [
{
key: 'measure',
// eslint-disable-next-line local-rules/use-metrickey-enum
params: [{ key: 'metric', possibleValues: ['alert_status', 'coverage'] }],
},
],
},
]),
}));

it('should renew token', async () => {
(getProjectBadgesToken as jest.Mock).mockResolvedValueOnce('foo').mockResolvedValueOnce('bar');
const wrapper = shallowRender({
const user = userEvent.setup();
jest.mocked(getProjectBadgesToken).mockResolvedValueOnce('foo').mockResolvedValueOnce('bar');
renderProjectBadges({
component: mockComponent({ configuration: { showSettings: true } }),
});
await waitAndUpdate(wrapper);
wrapper.find('.it__project-info-renew-badge').simulate('click');

// it shoud be loading
expect(wrapper.find('.it__project-info-renew-badge').props().disabled).toBe(true);
expect(
await screen.findByText(`overview.badges.get_badge.${ComponentQualifier.Project}`)
).toHaveFocus();

expect(screen.getByAltText(`overview.badges.${BadgeType.qualityGate}.alt`)).toHaveAttribute(
'src',
'host/api/project_badges/quality_gate?branch=branch-6.7&project=my-project&token=foo'
);

expect(screen.getByAltText(`overview.badges.${BadgeType.measure}.alt`)).toHaveAttribute(
'src',
'host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=alert_status&token=foo'
);

await user.click(screen.getByText('overview.badges.renew'));

await waitAndUpdate(wrapper);
const buttons = wrapper.find(BadgeButton);
expect(buttons.at(0).props().url).toMatch('token=bar');
expect(buttons.at(1).props().url).toMatch('token=bar');
expect(wrapper.find(CodeSnippet).props().snippet).toMatch('token=bar');
expect(
await screen.findByAltText(`overview.badges.${BadgeType.qualityGate}.alt`)
).toHaveAttribute(
'src',
'host/api/project_badges/quality_gate?branch=branch-6.7&project=my-project&token=bar'
);

// let's check that the loading has correclty ends.
expect(wrapper.find('.it__project-info-renew-badge').props().disabled).toBe(false);
expect(screen.getByAltText(`overview.badges.${BadgeType.measure}.alt`)).toHaveAttribute(
'src',
'host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=alert_status&token=bar'
);
});

it('should set focus on the heading when rendered', async () => {
const fakeElement = document.createElement('h3');
const focus = jest.fn();
(React.createRef as jest.Mock).mockReturnValueOnce({ current: { ...fakeElement, focus } });
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(focus).toHaveBeenCalled();
it('should update params', async () => {
renderProjectBadges({
component: mockComponent({ configuration: { showSettings: true } }),
});

expect(
await screen.findByText(
'[![alert_status](host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=alert_status&token=foo)](/dashboard)'
)
).toBeInTheDocument();

await selectEvent.select(screen.getByLabelText('format:'), [
'overview.badges.options.formats.url',
]);

expect(
screen.getByText(
'host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=alert_status&token=foo'
)
).toBeInTheDocument();

await selectEvent.select(screen.getByLabelText('overview.badges.metric:'), MetricKey.coverage);

expect(
screen.getByText(
`host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=${MetricKey.coverage}&token=foo`
)
).toBeInTheDocument();
});

function shallowRender(props: Partial<ProjectBadges['props']> = {}) {
return shallow<ProjectBadges>(
function renderProjectBadges(props: Partial<ProjectBadges['props']> = {}) {
return renderComponent(
<ProjectBadges
branchLike={mockBranch()}
component={mockComponent({ key: 'foo', qualifier: ComponentQualifier.Project })}

+ 0
- 39
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap View File

@@ -1,39 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should display correctly 1`] = `
<Button
className="badge-button"
onClick={[Function]}
>
<img
alt="overview.badges.quality_gate.alt"
src="http://foo.bar"
width="128px"
/>
</Button>
`;

exports[`should display correctly 2`] = `
<Button
className="badge-button selected"
onClick={[Function]}
>
<img
alt="overview.badges.quality_gate.alt"
src="http://foo.bar"
width="128px"
/>
</Button>
`;

exports[`should display correctly 3`] = `
<Button
className="badge-button"
onClick={[Function]}
>
<img
alt="overview.badges.measure.alt"
src="http://foo.bar"
/>
</Button>
`;

+ 0
- 87
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/BadgeParams-test.tsx.snap View File

@@ -1,87 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should display measure badge params 1`] = `
<div>
<label
className="spacer-right"
htmlFor="badge-metric"
>
overview.badges.metric
:
</label>
<Select
className="input-medium it__metric-badge-select"
isSearchable={false}
name="badge-metric"
onChange={[Function]}
options={[]}
/>
<label
className="spacer-right spacer-top"
htmlFor="badge-format"
>
format
:
</label>
<Select
className="input-medium"
defaultValue={
{
"label": "overview.badges.options.formats.md",
"value": "md",
}
}
isSearchable={false}
name="badge-format"
onChange={[Function]}
options={
[
{
"label": "overview.badges.options.formats.md",
"value": "md",
},
{
"label": "overview.badges.options.formats.url",
"value": "url",
},
]
}
/>
</div>
`;

exports[`should display quality gate badge params 1`] = `
<div>
<label
className="spacer-right"
htmlFor="badge-format"
>
format
:
</label>
<Select
className="input-medium"
defaultValue={
{
"label": "overview.badges.options.formats.md",
"value": "md",
}
}
isSearchable={false}
name="badge-format"
onChange={[Function]}
options={
[
{
"label": "overview.badges.options.formats.md",
"value": "md",
},
{
"label": "overview.badges.options.formats.url",
"value": "url",
},
]
}
/>
</div>
`;

+ 0
- 62
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/ProjectBadges-test.tsx.snap View File

@@ -1,62 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should display correctly 1`] = `
<div
className="display-flex-column"
>
<h3
tabIndex={-1}
>
overview.badges.get_badge.TRK
</h3>
<p
className="big-spacer-bottom"
>
overview.badges.description.TRK
</p>
<BadgeButton
onClick={[Function]}
selected={true}
type="measure"
url="host/api/project_badges/measure?branch=branch-6.7&project=foo&metric=alert_status&token=foo"
/>
<p
className="huge-spacer-bottom spacer-top"
>
overview.badges.measure.description.TRK
</p>
<BadgeButton
onClick={[Function]}
selected={false}
type="quality_gate"
url="host/api/project_badges/quality_gate?branch=branch-6.7&project=foo&token=foo"
/>
<p
className="huge-spacer-bottom spacer-top"
>
overview.badges.quality_gate.description.TRK
</p>
<withMetricsContext(BadgeParams)
className="big-spacer-bottom display-flex-column"
options={
{
"metric": "alert_status",
}
}
type="measure"
updateOptions={[Function]}
/>
<CodeSnippet
isOneLine={true}
snippet="[![alert_status](host/api/project_badges/measure?branch=branch-6.7&project=foo&metric=alert_status&token=foo)](/dashboard)"
/>
<Alert
variant="warning"
>
<p>
overview.badges.leak_warning
</p>
</Alert>
</div>
`;

+ 8
- 10
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaLinks.tsx View File

@@ -71,16 +71,14 @@ export default class MetaLinks extends React.PureComponent<Props, State> {
const orderedLinks = orderLinks(links);

return (
<>
<div className="big-padded bordered-bottom">
<h3>{translate('overview.external_links')}</h3>
<ul className="project-info-list">
{orderedLinks.map((link) => (
<MetaLink key={link.id} link={link} />
))}
</ul>
</div>
</>
<div className="big-padded bordered-bottom">
<h3>{translate('overview.external_links')}</h3>
<ul className="project-info-list">
{orderedLinks.map((link) => (
<MetaLink key={link.id} link={link} />
))}
</ul>
</div>
);
}
}

+ 29
- 42
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaTags.tsx View File

@@ -34,20 +34,11 @@ interface Props {
}

export default class MetaTags extends React.PureComponent<Props> {
card?: HTMLDivElement | null;
tagsList?: HTMLElement | null;
tagsSelector?: HTMLDivElement | null;

canUpdateTags = () => {
const { configuration } = this.props.component;
return configuration && configuration.showSettings;
};

getPopupPos = (eltPos: ClientRect, containerPos: ClientRect) => ({
top: eltPos.height,
right: containerPos.width - eltPos.width,
});

setTags = (values: string[]) => {
const { component } = this.props;

@@ -56,12 +47,12 @@ export default class MetaTags extends React.PureComponent<Props> {
application: component.key,
tags: values.join(','),
});
} else {
return setProjectTags({
project: component.key,
tags: values.join(','),
});
}

return setProjectTags({
project: component.key,
tags: values.join(','),
});
};

handleSetProjectTags = (values: string[]) => {
@@ -74,33 +65,29 @@ export default class MetaTags extends React.PureComponent<Props> {
render() {
const tags = this.props.component.tags || [];

if (this.canUpdateTags()) {
return (
<div className="big-spacer-top project-info-tags" ref={(card) => (this.card = card)}>
<Dropdown
closeOnClick={false}
closeOnClickOutside={true}
overlay={
<MetaTagsSelector selectedTags={tags} setProjectTags={this.handleSetProjectTags} />
}
overlayPlacement={PopupPlacement.BottomLeft}
>
<ButtonLink innerRef={(tagsList) => (this.tagsList = tagsList)} stopPropagation={true}>
<TagsList allowUpdate={true} tags={tags.length ? tags : [translate('no_tags')]} />
</ButtonLink>
</Dropdown>
</div>
);
} else {
return (
<div className="big-spacer-top project-info-tags">
<TagsList
allowUpdate={false}
className="note"
tags={tags.length ? tags : [translate('no_tags')]}
/>
</div>
);
}
return this.canUpdateTags() ? (
<div className="big-spacer-top project-info-tags">
<Dropdown
closeOnClick={false}
closeOnClickOutside={true}
overlay={
<MetaTagsSelector selectedTags={tags} setProjectTags={this.handleSetProjectTags} />
}
overlayPlacement={PopupPlacement.BottomLeft}
>
<ButtonLink stopPropagation={true}>
<TagsList allowUpdate={true} tags={tags.length ? tags : [translate('no_tags')]} />
</ButtonLink>
</Dropdown>
</div>
) : (
<div className="big-spacer-top project-info-tags">
<TagsList
allowUpdate={false}
className="note"
tags={tags.length ? tags : [translate('no_tags')]}
/>
</div>
);
}
}

+ 0
- 72
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaLink-test.tsx View File

@@ -1,72 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { ClearButton } from '../../../../../../../components/controls/buttons';
import { click } from '../../../../../../../helpers/testUtils';
import MetaLink from '../MetaLink';

const DANGEROUS_LINK = {
id: '1',
name: 'Dangerous',
url: 'javascript:alert("hi")',
type: 'dangerous',
};

it('should match snapshot', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ iconOnly: true })).toMatchSnapshot('icon only');
const wrapper = shallowRender({ link: DANGEROUS_LINK });
expect(wrapper).toMatchSnapshot('dangerous link, collapsed');
wrapper.setState({ expanded: true });
expect(wrapper).toMatchSnapshot('dangerous link, expanded');
});

it('should expand and collapse dangerous links', () => {
const wrapper = shallowRender({ link: DANGEROUS_LINK });
expect(wrapper.state().expanded).toBe(false);

// expand
click(wrapper.find('a'));
expect(wrapper.state().expanded).toBe(true);

// collapse
click(wrapper.find('a'));
expect(wrapper.state().expanded).toBe(false);

// collapse with button
wrapper.setState({ expanded: true });
click(wrapper.find(ClearButton));
expect(wrapper.state().expanded).toBe(false);
});

function shallowRender(props: Partial<MetaLink['props']> = {}) {
return shallow<MetaLink>(
<MetaLink
link={{
id: '1',
name: 'Foo',
url: 'http://example.com',
type: 'foo',
}}
{...props}
/>
);
}

+ 27
- 14
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaQualityProfiles-test.tsx View File

@@ -17,11 +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 { shallow } from 'enzyme';
import { screen } from '@testing-library/react';
import * as React from 'react';
import { searchRules } from '../../../../../../../api/rules';
import { mockLanguage, mockQualityProfile } from '../../../../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../../../../helpers/testUtils';
import { renderComponent } from '../../../../../../../helpers/testReactTestingUtils';
import { SearchRulesResponse } from '../../../../../../../types/coding-rules';
import { Dict } from '../../../../../../../types/types';
import { MetaQualityProfiles } from '../MetaQualityProfiles';

jest.mock('../../../../../../../api/rules', () => {
@@ -33,28 +35,39 @@ jest.mock('../../../../../../../api/rules', () => {
});

it('should render correctly', async () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
const totals: Dict<number> = {
js: 0,
ts: 10,
css: 0,
};
jest.mocked(searchRules).mockImplementation(({ qprofile }: { qprofile: string }) => {
return Promise.resolve({ total: totals[qprofile] } as SearchRulesResponse);
});

renderMetaQualityprofiles();

await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('.project-info-deprecated-rules').exists()).toBe(true);
expect(wrapper.find('.project-info-deleted-profile').exists()).toBe(true);
expect(searchRules).toHaveBeenCalled();
expect(await screen.findByText('overview.deleted_profile.javascript')).toBeInTheDocument();
expect(screen.getByText('overview.deprecated_profile.10')).toBeInTheDocument();
});

function shallowRender(props: Partial<MetaQualityProfiles['props']> = {}) {
return shallow(
function renderMetaQualityprofiles(overrides: Partial<MetaQualityProfiles['props']> = {}) {
return renderComponent(
<MetaQualityProfiles
languages={{ css: mockLanguage() }}
profiles={[
{ ...mockQualityProfile({ key: 'js' }), deleted: true },
{ ...mockQualityProfile({ key: 'js', name: 'javascript' }), deleted: true },
{ ...mockQualityProfile({ key: 'ts', name: 'typescript' }), deleted: false },
{
...mockQualityProfile({ key: 'css', language: 'css', languageName: 'CSS' }),
...mockQualityProfile({
key: 'css',
name: 'style',
language: 'css',
languageName: 'CSS',
}),
deleted: false,
},
]}
{...props}
{...overrides}
/>
);
}

+ 0
- 46
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaSize-test.tsx View File

@@ -1,46 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockComponent } from '../../../../../../../helpers/mocks/component';
import { mockMeasure } from '../../../../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../../../../types/component';
import { MetricKey } from '../../../../../../../types/metrics';
import MetaSize, { MetaSizeProps } from '../MetaSize';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('project');
expect(
shallowRender({ component: mockComponent({ qualifier: ComponentQualifier.Application }) })
).toMatchSnapshot('application');
});

function shallowRender(props: Partial<MetaSizeProps> = {}) {
return shallow<MetaSizeProps>(
<MetaSize
component={mockComponent()}
measures={[
mockMeasure({ metric: MetricKey.ncloc }),
mockMeasure({ metric: MetricKey.projects }),
]}
{...props}
/>
);
}

+ 51
- 16
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaTags-test.tsx View File

@@ -17,27 +17,41 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
import { setApplicationTags, setProjectTags } from '../../../../../../../api/components';
import {
searchProjectTags,
setApplicationTags,
setProjectTags,
} from '../../../../../../../api/components';
import { mockComponent } from '../../../../../../../helpers/mocks/component';
import { renderComponent } from '../../../../../../../helpers/testReactTestingUtils';
import { ComponentQualifier } from '../../../../../../../types/component';
import MetaTags from '../MetaTags';

jest.mock('../../../../../../../api/components', () => ({
setApplicationTags: jest.fn().mockResolvedValue(true),
setProjectTags: jest.fn().mockResolvedValue(true),
searchProjectTags: jest.fn(),
}));

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

it('should render without tags and admin rights', () => {
expect(shallowRender()).toMatchSnapshot();
it('should render without tags and admin rights', async () => {
renderMetaTags();

expect(await screen.findByText('no_tags')).toBeInTheDocument();
expect(screen.queryByRole('button')).not.toBeInTheDocument();
});

it('should render with tags and admin rights', () => {
it('should allow to edit tags for a project', async () => {
const user = userEvent.setup();
jest.mocked(searchProjectTags).mockResolvedValue({ tags: ['best', 'useless'] });

const onComponentChange = jest.fn();
const component = mockComponent({
key: 'my-second-project',
tags: ['foo', 'bar'],
@@ -47,37 +61,58 @@ it('should render with tags and admin rights', () => {
name: 'MySecondProject',
});

expect(shallowRender({ component })).toMatchSnapshot();
});
renderMetaTags({ component, onComponentChange });

expect(await screen.findByText('foo, bar')).toBeInTheDocument();
expect(screen.getByRole('button')).toBeInTheDocument();

await user.click(screen.getByRole('button', { name: 'tags_list_x.foo, bar' }));

it('should set tags for a project', () => {
const wrapper = shallowRender();
expect(await screen.findByText('best')).toBeInTheDocument();

wrapper.instance().handleSetProjectTags(['tag1', 'tag2']);
await user.click(screen.getByText('best'));
expect(onComponentChange).toHaveBeenCalledWith({ tags: ['foo', 'bar', 'best'] });

onComponentChange.mockClear();

/*
* Since we're not actually updating the tags, we're back to having the foo, bar only
*/
await user.click(screen.getByText('bar'));
expect(onComponentChange).toHaveBeenCalledWith({ tags: ['foo'] });

expect(setProjectTags).toHaveBeenCalled();
expect(setApplicationTags).not.toHaveBeenCalled();
});

it('should set tags for an app', () => {
const wrapper = shallowRender({
component: mockComponent({ qualifier: ComponentQualifier.Application }),
it('should set tags for an app', async () => {
const user = userEvent.setup();

renderMetaTags({
component: mockComponent({
configuration: {
showSettings: true,
},
qualifier: ComponentQualifier.Application,
}),
});

wrapper.instance().handleSetProjectTags(['tag1', 'tag2']);
await user.click(screen.getByRole('button', { name: 'tags_list_x.no_tags' }));

await user.click(screen.getByText('best'));

expect(setProjectTags).not.toHaveBeenCalled();
expect(setApplicationTags).toHaveBeenCalled();
});

function shallowRender(overrides: Partial<MetaTags['props']> = {}) {
function renderMetaTags(overrides: Partial<MetaTags['props']> = {}) {
const component = mockComponent({
configuration: {
showSettings: false,
},
});

return shallow<MetaTags>(
return renderComponent(
<MetaTags component={component} onComponentChange={jest.fn()} {...overrides} />
);
}

+ 0
- 57
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaTagsSelector-test.tsx View File

@@ -1,57 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/* eslint-disable import/first */
import { mount, shallow } from 'enzyme';
import * as React from 'react';
import { searchProjectTags } from '../../../../../../../api/components';
import MetaTagsSelector from '../MetaTagsSelector';

jest.mock('../../../../../../../api/components', () => ({
searchProjectTags: jest.fn(),
}));

jest.mock('lodash', () => {
const lodash = jest.requireActual('lodash');
lodash.debounce = jest.fn((fn) => fn);
return lodash;
});

it('searches tags on mount', () => {
(searchProjectTags as jest.Mock).mockImplementation(() =>
Promise.resolve({ tags: ['foo', 'bar'] })
);
mount(<MetaTagsSelector selectedTags={[]} setProjectTags={jest.fn()} />);
expect(searchProjectTags).toHaveBeenCalledWith({ ps: 9, q: '' });
});

it('selects and deselects tags', () => {
const setProjectTags = jest.fn();
const wrapper = shallow(
<MetaTagsSelector selectedTags={['foo', 'bar']} setProjectTags={setProjectTags} />
);

const tagSelect: any = wrapper.find('TagsSelector');
tagSelect.prop('onSelect')('baz');
expect(setProjectTags).toHaveBeenLastCalledWith(['foo', 'bar', 'baz']);

// note that the `selectedTags` is a prop and so it wasn't changed
tagSelect.prop('onUnselect')('bar');
expect(setProjectTags).toHaveBeenLastCalledWith(['foo']);
});

+ 0
- 87
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaLink-test.tsx.snap View File

@@ -1,87 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should match snapshot: dangerous link, collapsed 1`] = `
<li>
<a
className="link-no-underline"
onClick={[Function]}
rel="nofollow noreferrer noopener"
target="_blank"
title="Dangerous"
>
<ProjectLinkIcon
className="little-spacer-right"
type="dangerous"
/>
Dangerous
</a>
</li>
`;

exports[`should match snapshot: dangerous link, expanded 1`] = `
<li>
<a
className="link-no-underline"
onClick={[Function]}
rel="nofollow noreferrer noopener"
target="_blank"
title="Dangerous"
>
<ProjectLinkIcon
className="little-spacer-right"
type="dangerous"
/>
Dangerous
</a>
<div
className="little-spacer-top display-flex-center"
>
<input
className="overview-key width-80"
onClick={[Function]}
readOnly={true}
type="text"
value="javascript:alert("hi")"
/>
<ClearButton
className="little-spacer-left"
onClick={[Function]}
/>
</div>
</li>
`;

exports[`should match snapshot: default 1`] = `
<li>
<a
className="link-no-underline"
href="http://example.com"
rel="nofollow noreferrer noopener"
target="_blank"
title="Foo"
>
<ProjectLinkIcon
className="little-spacer-right"
type="foo"
/>
Foo
</a>
</li>
`;

exports[`should match snapshot: icon only 1`] = `
<li>
<a
className="link-no-underline"
href="http://example.com"
rel="nofollow noreferrer noopener"
target="_blank"
title="Foo"
>
<ProjectLinkIcon
className="little-spacer-right"
type="foo"
/>
</a>
</li>
`;

+ 0
- 130
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaQualityProfiles-test.tsx.snap View File

@@ -1,130 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<Fragment>
<h3>
overview.quality_profiles
</h3>
<ul
className="project-info-list"
>
<Tooltip
key="js"
overlay="overview.deleted_profile.name"
>
<li
className="project-info-deleted-profile"
>
<div
className="text-ellipsis"
>
<span
className="spacer-right"
>
(
js
)
</span>
name
</div>
</li>
</Tooltip>
<li
key="css"
>
<div
className="text-ellipsis"
>
<span
className="spacer-right"
>
(
CSS
)
</span>
<ForwardRef(Link)
to={
{
"pathname": "/profiles/show",
"search": "?name=name&language=css",
}
}
>
<span
aria-label="overview.link_to_x_profile_y.CSS.name"
>
name
</span>
</ForwardRef(Link)>
</div>
</li>
</ul>
</Fragment>
`;

exports[`should render correctly 2`] = `
<Fragment>
<h3>
overview.quality_profiles
</h3>
<ul
className="project-info-list"
>
<Tooltip
key="js"
overlay="overview.deleted_profile.name"
>
<li
className="project-info-deleted-profile"
>
<div
className="text-ellipsis"
>
<span
className="spacer-right"
>
(
js
)
</span>
name
</div>
</li>
</Tooltip>
<Tooltip
key="css"
overlay="overview.deprecated_profile.10"
>
<li
className="project-info-deprecated-rules"
>
<div
className="text-ellipsis"
>
<span
className="spacer-right"
>
(
CSS
)
</span>
<ForwardRef(Link)
to={
{
"pathname": "/profiles/show",
"search": "?name=name&language=css",
}
}
>
<span
aria-label="overview.link_to_x_profile_y.CSS.name"
>
name
</span>
</ForwardRef(Link)>
</div>
</li>
</Tooltip>
</ul>
</Fragment>
`;

+ 0
- 102
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaSize-test.tsx.snap View File

@@ -1,102 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: application 1`] = `
<Fragment>
<div
className="display-flex-row display-inline-flex-baseline"
>
<h3>
metric.ncloc.name
</h3>
<span
className="spacer-left small"
>
(
project.info.main_branch
)
</span>
</div>
<div
className="display-flex-center"
>
<DrilldownLink
className="huge"
component="my-project"
metric="ncloc"
>
<span
aria-label="project.info.see_more_info_on_x_locs.1.0"
>
1
</span>
</DrilldownLink>
<span
className="spacer-left"
>
<SizeRating
value={1}
/>
</span>
<span
className="huge-spacer-left display-inline-flex-center"
>
<DrilldownLink
component="my-project"
metric="projects"
>
<span
className="big"
>
1
</span>
</DrilldownLink>
<span
className="little-spacer-left text-muted"
>
metric.projects.name
</span>
</span>
</div>
</Fragment>
`;

exports[`should render correctly: project 1`] = `
<Fragment>
<div
className="display-flex-row display-inline-flex-baseline"
>
<h3>
metric.ncloc.name
</h3>
<span
className="spacer-left small"
>
(
project.info.main_branch
)
</span>
</div>
<div
className="display-flex-center"
>
<DrilldownLink
className="huge"
component="my-project"
metric="ncloc"
>
<span
aria-label="project.info.see_more_info_on_x_locs.1.0"
>
1
</span>
</DrilldownLink>
<span
className="spacer-left"
>
<SizeRating
value={1}
/>
</span>
</div>
</Fragment>
`;

+ 0
- 55
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap View File

@@ -1,55 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render with tags and admin rights 1`] = `
<div
className="big-spacer-top project-info-tags"
>
<Dropdown
closeOnClick={false}
closeOnClickOutside={true}
overlay={
<MetaTagsSelector
selectedTags={
[
"foo",
"bar",
]
}
setProjectTags={[Function]}
/>
}
overlayPlacement="bottom-left"
>
<ButtonLink
innerRef={[Function]}
stopPropagation={true}
>
<TagsList
allowUpdate={true}
tags={
[
"foo",
"bar",
]
}
/>
</ButtonLink>
</Dropdown>
</div>
`;

exports[`should render without tags and admin rights 1`] = `
<div
className="big-spacer-top project-info-tags"
>
<TagsList
allowUpdate={false}
className="note"
tags={
[
"no_tags",
]
}
/>
</div>
`;

+ 62
- 62
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/notifications/__tests__/ProjectNotifications-test.tsx View File

@@ -17,83 +17,83 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
import { getNotifications } from '../../../../../../../api/notifications';
import { mockComponent } from '../../../../../../../helpers/mocks/component';
import { mockNotification } from '../../../../../../../helpers/testMocks';
import { renderComponent } from '../../../../../../../helpers/testReactTestingUtils';
import {
NotificationGlobalType,
NotificationProjectType,
} from '../../../../../../../types/notifications';
import { ProjectNotifications } from '../ProjectNotifications';
import ProjectNotifications from '../ProjectNotifications';

jest.mock('react', () => {
return {
...jest.requireActual('react'),
useEffect: jest.fn().mockImplementation((f) => f()),
useRef: jest.fn().mockReturnValue({ current: document.createElement('h3') }),
};
});
jest.mock('../../../../../../../api/notifications', () => ({
addNotification: jest.fn().mockResolvedValue(undefined),
removeNotification: jest.fn().mockResolvedValue(undefined),
getNotifications: jest.fn(),
}));

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
beforeAll(() => {
jest.mocked(getNotifications).mockResolvedValue({
channels: ['channel1'],
globalTypes: [NotificationGlobalType.MyNewIssues],
notifications: [
mockNotification({}),
mockNotification({ type: NotificationProjectType.NewAlerts }),
],
perProjectTypes: [NotificationProjectType.NewAlerts, NotificationProjectType.NewIssues],
});
});

it('should add and remove a notification for the project', () => {
const addNotification = jest.fn();
const removeNotification = jest.fn();
const wrapper = shallowRender({ addNotification, removeNotification });
const notification = {
channel: 'EmailNotificationChannel',
type: 'SQ-MyNewIssues',
};
it('should render correctly', async () => {
const user = userEvent.setup();
renderProjectNotifications();

wrapper.find('NotificationsList').prop<Function>('onAdd')(notification);
expect(addNotification).toHaveBeenCalledWith({ ...notification, project: 'foo' });
expect(await screen.findByText('notification.channel.channel1')).toBeInTheDocument();
expect(
screen.getByLabelText(
'notification.dispatcher.descrption_x.notification.dispatcher.NewAlerts.project'
)
).toBeChecked();

wrapper.find('NotificationsList').prop<Function>('onRemove')(notification);
expect(removeNotification).toHaveBeenCalledWith({ ...notification, project: 'foo' });
});
expect(
screen.getByLabelText(
'notification.dispatcher.descrption_x.notification.dispatcher.NewIssues.project'
)
).not.toBeChecked();

// Toggle New Alerts
await user.click(
screen.getByLabelText(
'notification.dispatcher.descrption_x.notification.dispatcher.NewAlerts.project'
)
);

expect(
screen.getByLabelText(
'notification.dispatcher.descrption_x.notification.dispatcher.NewAlerts.project'
)
).not.toBeChecked();

it('should set focus on the heading when rendered', () => {
const fakeElement = document.createElement('h3');
const focus = jest.fn();
(React.useRef as jest.Mock).mockReturnValueOnce({ current: { ...fakeElement, focus } });
// Toggle New Issues
await user.click(
screen.getByLabelText(
'notification.dispatcher.descrption_x.notification.dispatcher.NewIssues.project'
)
);

shallowRender();
expect(focus).toHaveBeenCalled();
expect(
screen.getByLabelText(
'notification.dispatcher.descrption_x.notification.dispatcher.NewIssues.project'
)
).toBeChecked();
});

function shallowRender(props = {}) {
return shallow(
<ProjectNotifications
addNotification={jest.fn()}
channels={['channel1', 'channel2']}
component={mockComponent({ key: 'foo' })}
globalTypes={[NotificationGlobalType.CeReportTaskFailure, NotificationGlobalType.NewAlerts]}
loading={false}
notifications={[
{
channel: 'channel1',
type: 'type-global',
project: 'foo',
projectName: 'Foo',
},
{
channel: 'channel1',
type: 'type-common',
project: 'bar',
projectName: 'Bar',
},
{
channel: 'channel2',
type: 'type-common',
project: 'qux',
projectName: 'Qux',
},
]}
perProjectTypes={[NotificationProjectType.CeReportTaskFailure]}
removeNotification={jest.fn()}
{...props}
/>
function renderProjectNotifications() {
return renderComponent(
<ProjectNotifications component={mockComponent({ key: 'foo', name: 'Foo' })} />
);
}

+ 0
- 76
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/notifications/__tests__/__snapshots__/ProjectNotifications-test.tsx.snap View File

@@ -1,76 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<Fragment>
<h3
tabIndex={-1}
>
project.info.notifications
</h3>
<Alert
aria-live="off"
className="spacer-top"
variant="info"
>
notification.dispatcher.information
</Alert>
<DeferredSpinner
loading={false}
>
<table
className="data zebra notifications-table"
>
<thead>
<tr>
<th
aria-label="project"
/>
<th
className="text-center"
key="channel1"
>
<h4>
notification.channel.channel1
</h4>
</th>
<th
className="text-center"
key="channel2"
>
<h4>
notification.channel.channel2
</h4>
</th>
</tr>
</thead>
<NotificationsList
channels={
[
"channel1",
"channel2",
]
}
checkboxId={[Function]}
notifications={
[
{
"channel": "channel1",
"project": "foo",
"projectName": "Foo",
"type": "type-global",
},
]
}
onAdd={[Function]}
onRemove={[Function]}
project={true}
types={
[
"CeReportTaskFailure",
]
}
/>
</table>
</DeferredSpinner>
</Fragment>
`;

+ 11
- 0
server/sonar-web/src/main/js/helpers/testMocks.ts View File

@@ -27,6 +27,7 @@ import { RuleRepository } from '../types/coding-rules';
import { EditionKey } from '../types/editions';
import { IssueScope, IssueSeverity, IssueStatus, IssueType, RawIssue } from '../types/issues';
import { Language } from '../types/languages';
import { Notification } from '../types/notifications';
import { DumpStatus, DumpTask } from '../types/project-dump';
import { TaskStatuses } from '../types/tasks';
import {
@@ -414,6 +415,16 @@ export function mockMeasureEnhanced(overrides: Partial<MeasureEnhanced> = {}): M
};
}

export function mockNotification(overrides: Partial<Notification> = {}): Notification {
return {
channel: 'channel1',
type: 'type-global',
project: 'foo',
projectName: 'Foo',
...overrides,
};
}

export function mockPeriod(overrides: Partial<Period> = {}): Period {
return {
date: '2019-04-23T02:12:32+0100',

Loading…
Cancel
Save