component: Component;
}
-export function ComponentNavProjectBindingErrorNotif(
+export default function ComponentNavProjectBindingErrorNotif(
props: ComponentNavProjectBindingErrorNotifProps
) {
const { component } = props;
</Alert>
);
}
-
-export default ComponentNavProjectBindingErrorNotif;
* 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} />
);
}
+++ /dev/null
-// 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>
-`;
</label>
<Select
className="input-medium it__metric-badge-select"
- name="badge-metric"
+ inputId="badge-metric"
isSearchable={false}
onChange={this.handleMetricChange}
options={metricOptions}
</label>
<Select
className="input-medium"
- name="badge-format"
+ inputId="badge-format"
isSearchable={false}
onChange={this.handleFormatChange}
options={formatOptions}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
* 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',
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 })}
+++ /dev/null
-// 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>
-`;
+++ /dev/null
-// 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>
-`;
+++ /dev/null
-// 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>
-`;
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>
);
}
}
}
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;
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[]) => {
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>
+ );
}
}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
* 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', () => {
});
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}
/>
);
}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
* 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'],
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} />
);
}
+++ /dev/null
-/*
- * 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']);
-});
+++ /dev/null
-// 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>
-`;
+++ /dev/null
-// 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>
-`;
+++ /dev/null
-// 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>
-`;
+++ /dev/null
-// 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>
-`;
* 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' })} />
);
}
+++ /dev/null
-// 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>
-`;
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 {
};
}
+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',