* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { LargeCenteredLayout } from 'design-system';
import * as React from 'react';
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
import { parseDate } from '../../../helpers/dates';
detectedCIOnLastAnalysis={detectedCIOnLastAnalysis}
projectBinding={projectBinding}
/>
- <div className="page page-limited">
- <div className="overview">
+ <LargeCenteredLayout>
+ <div className="overview sw-mt-6">
<A11ySkipTarget anchor="overview_main" />
{projectIsEmpty ? (
</div>
)}
</div>
- </div>
+ </LargeCenteredLayout>
</>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import {
+ ButtonSecondary,
+ ChevronDownIcon,
+ Dropdown,
+ ItemButton,
+ ItemDownload,
+ PopupPlacement,
+ PopupZLevel,
+} from 'design-system';
import * as React from 'react';
import { getReportUrl } from '../../api/component-report';
-import Dropdown from '../../components/controls/Dropdown';
-import { Button } from '../../components/controls/buttons';
-import DropdownIcon from '../../components/icons/DropdownIcon';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { Branch } from '../../types/branch-like';
import { Component } from '../../types/types';
handleUnsubscription: () => void;
}
-export default function ComponentReportActionsRenderer(props: ComponentReportActionsRendererProps) {
- const { branch, component, frequency, subscribed, canSubscribe, currentUserHasEmail } = props;
+const getSubscriptionText = ({
+ currentUserHasEmail,
+ frequency,
+ subscribed,
+}: Pick<
+ ComponentReportActionsRendererProps,
+ 'currentUserHasEmail' | 'frequency' | 'subscribed'
+>) => {
+ if (!currentUserHasEmail) {
+ return translate('component_report.no_email_to_subscribe');
+ }
- const renderDownloadButton = (simple = false) => {
- return (
- <a
- download={[component.name, branch?.name, 'PDF Report.pdf'].filter((s) => !!s).join(' - ')}
- href={getReportUrl(component.key, branch?.name)}
- target="_blank"
- rel="noopener noreferrer"
- >
- {simple
- ? translate('download_verb')
- : translateWithParameters(
- 'component_report.download',
- translate('qualifier', component.qualifier).toLowerCase()
- )}
- </a>
- );
- };
+ const translationKey = subscribed
+ ? 'component_report.unsubscribe_x'
+ : 'component_report.subscribe_x';
+ const frequencyTranslation = translate('report.frequency', frequency).toLowerCase();
- const renderSubscriptionButton = () => {
- if (!currentUserHasEmail) {
- return (
- <span className="text-muted-2">{translate('component_report.no_email_to_subscribe')}</span>
- );
- }
+ return translateWithParameters(translationKey, frequencyTranslation);
+};
- const translationKey = subscribed
- ? 'component_report.unsubscribe_x'
- : 'component_report.subscribe_x';
- const onClickHandler = subscribed ? props.handleUnsubscription : props.handleSubscription;
- const frequencyTranslation = translate('report.frequency', frequency).toLowerCase();
+export default function ComponentReportActionsRenderer(props: ComponentReportActionsRendererProps) {
+ const { branch, component, frequency, subscribed, canSubscribe, currentUserHasEmail } = props;
- return (
- <a href="#" onClick={onClickHandler} data-test="overview__subscribe-to-report-button">
- {translateWithParameters(translationKey, frequencyTranslation)}
- </a>
- );
- };
+ const downloadName = [component.name, branch?.name, 'PDF Report.pdf']
+ .filter((s) => !!s)
+ .join(' - ');
+ const reportUrl = getReportUrl(component.key, branch?.name);
return canSubscribe ? (
<Dropdown
+ id="component-report"
+ size="auto"
+ placement={PopupPlacement.BottomRight}
+ zLevel={PopupZLevel.Default}
overlay={
- <ul className="menu">
- <li>{renderDownloadButton(true)}</li>
- <li>{renderSubscriptionButton()}</li>
- </ul>
+ <>
+ <ItemDownload download={downloadName} href={reportUrl}>
+ {translate('download_verb')}
+ </ItemDownload>
+ <ItemButton
+ disabled={!currentUserHasEmail}
+ data-test="overview__subscribe-to-report-button"
+ onClick={subscribed ? props.handleUnsubscription : props.handleSubscription}
+ >
+ {getSubscriptionText({ currentUserHasEmail, frequency, subscribed })}
+ </ItemButton>
+ </>
}
>
- <Button className="dropdown-toggle">
+ <ButtonSecondary>
{translateWithParameters(
'component_report.report',
translate('qualifier', component.qualifier)
)}
- <DropdownIcon className="spacer-left" />
- </Button>
+ <ChevronDownIcon className="sw-ml-1" />
+ </ButtonSecondary>
</Dropdown>
) : (
- renderDownloadButton()
+ <a download={downloadName} href={reportUrl} target="_blank" rel="noopener noreferrer">
+ {translateWithParameters(
+ 'component_report.download',
+ translate('qualifier', component.qualifier).toLowerCase()
+ )}
+ </a>
);
}
* 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 {
getReportStatus,
subscribeToEmailReport,
unsubscribeFromEmailReport,
} from '../../../api/component-report';
-import { addGlobalSuccessMessage } from '../../../helpers/globalMessages';
import { mockBranch } from '../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../helpers/mocks/component';
import { mockComponentReportStatus } from '../../../helpers/mocks/component-report';
-import { mockAppState, mockCurrentUser } from '../../../helpers/testMocks';
-import { waitAndUpdate } from '../../../helpers/testUtils';
+import { mockAppState, mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
+import { renderApp } from '../../../helpers/testReactTestingUtils';
import { ComponentQualifier } from '../../../types/component';
import { ComponentReportActions } from '../ComponentReportActions';
getBaseUrl: jest.fn().mockReturnValue('baseUrl'),
}));
-jest.mock('../../../helpers/globalMessages', () => ({
- addGlobalSuccessMessage: jest.fn(),
-}));
-
beforeEach(jest.clearAllMocks);
-it('should not render anything', async () => {
- // loading
- expect(shallowRender().type()).toBeNull();
+it('should not render anything when no status', async () => {
+ jest.mocked(getReportStatus).mockRejectedValueOnce('Nope');
+
+ renderComponentReportActions();
+
+ // Loading
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
+
+ await new Promise(setImmediate); // Make sure we wait until we're done loading
// No status
- (getReportStatus as jest.Mock).mockResolvedValueOnce(undefined);
- const w1 = shallowRender();
- await waitAndUpdate(w1);
- expect(w1.type()).toBeNull();
-
- // Branch purgeable
- const w2 = shallowRender({ branch: mockBranch({ excludedFromPurge: false }) });
- await waitAndUpdate(w2);
- expect(w2.type()).toBeNull();
-
- // no governance
- const w3 = shallowRender({ appState: mockAppState({ qualifiers: [] }) });
- await waitAndUpdate(w3);
- expect(w3.type()).toBeNull();
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
});
-it('should call for status properly', async () => {
- const component = mockComponent();
- const branch = mockBranch();
+it('should not render anything when branch is purgeable', async () => {
+ renderComponentReportActions({
+ branch: mockBranch({ excludedFromPurge: false }),
+ });
- const wrapper = shallowRender({ component, branch });
+ await new Promise(setImmediate); // Make sure we wait until we're done loading
- await waitAndUpdate(wrapper);
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
+});
- expect(getReportStatus).toHaveBeenCalledWith(component.key, branch.name);
+it('should not render anything without governance', async () => {
+ renderComponentReportActions({ appState: mockAppState({ qualifiers: [] }) });
+
+ await new Promise(setImmediate); // Make sure we wait until we're done loading
+
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
});
-it('should handle subscription', async () => {
+it('should allow user to (un)subscribe', async () => {
+ jest
+ .mocked(getReportStatus)
+ .mockResolvedValueOnce(mockComponentReportStatus({ globalFrequency: 'monthly' }))
+ .mockResolvedValueOnce(
+ mockComponentReportStatus({ subscribed: true, globalFrequency: 'monthly' })
+ );
+
+ const user = userEvent.setup();
const component = mockComponent();
const branch = mockBranch();
- const wrapper = shallowRender({ component, branch });
- await wrapper.instance().handleSubscribe();
+ renderComponentReportActions({
+ component,
+ branch,
+ currentUser: mockLoggedInUser({ email: 'igot@nEmail.address' }),
+ });
+
+ expect(getReportStatus).toHaveBeenCalledWith(component.key, branch.name);
+
+ const button = await screen.findByRole('button', {
+ name: 'component_report.report.qualifier.TRK',
+ });
+ expect(button).toBeInTheDocument();
+ await user.click(button);
+
+ expect(screen.getByText('download_verb')).toBeInTheDocument();
+
+ // Subscribe!
+ const subscribeButton = screen.getByText('component_report.subscribe_x.report.frequency.monthly');
+ expect(subscribeButton).toBeInTheDocument();
+
+ await user.click(subscribeButton);
expect(subscribeToEmailReport).toHaveBeenCalledWith(component.key, branch.name);
- expect(addGlobalSuccessMessage).toHaveBeenCalledWith(
- 'component_report.subscribe_x_success.report.frequency..qualifier.trk'
- );
-});
+ expect(await screen.findByRole('status')).toBeInTheDocument();
-it('should handle unsubscription', async () => {
- const component = mockComponent();
- const branch = mockBranch();
- const wrapper = shallowRender({ component, branch });
+ await new Promise(setImmediate);
- await waitAndUpdate(wrapper);
+ // And unsubscribe!
+ await user.click(button);
- wrapper.setState({ status: mockComponentReportStatus({ componentFrequency: 'compfreq' }) });
+ const unsubscribeButton = screen.getByText(
+ 'component_report.unsubscribe_x.report.frequency.monthly'
+ );
+ expect(unsubscribeButton).toBeInTheDocument();
- await wrapper.instance().handleUnsubscribe();
+ await user.click(unsubscribeButton);
expect(unsubscribeFromEmailReport).toHaveBeenCalledWith(component.key, branch.name);
- expect(addGlobalSuccessMessage).toHaveBeenCalledWith(
- 'component_report.unsubscribe_x_success.report.frequency.compfreq.qualifier.trk'
+ expect(screen.getAllByRole('status')).toHaveLength(2);
+});
+
+it('should prevent user to subscribe if no email', async () => {
+ const user = userEvent.setup();
+
+ renderComponentReportActions({ currentUser: mockLoggedInUser({ email: undefined }) });
+
+ await new Promise(setImmediate);
+
+ await user.click(
+ await screen.findByRole('button', {
+ name: 'component_report.report.qualifier.TRK',
+ })
);
+
+ const subscribeButton = screen.getByText('component_report.no_email_to_subscribe');
+ expect(subscribeButton).toBeInTheDocument();
+ expect(subscribeButton).toBeDisabled();
});
-function shallowRender(props: Partial<ComponentReportActions['props']> = {}) {
- return shallow<ComponentReportActions>(
+function renderComponentReportActions(props: Partial<ComponentReportActions['props']> = {}) {
+ return renderApp(
+ '/',
<ComponentReportActions
appState={mockAppState({ qualifiers: [ComponentQualifier.Portfolio] })}
component={mockComponent()}
+++ /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 { ComponentQualifier } from '../../../types/component';
-import ComponentReportActionsRenderer, {
- ComponentReportActionsRendererProps,
-} from '../ComponentReportActionsRenderer';
-
-it('should render correctly', () => {
- expect(shallowRender({ canSubscribe: false })).toMatchSnapshot('cannot subscribe');
- expect(shallowRender({ canSubscribe: true, subscribed: false })).toMatchSnapshot(
- 'can subscribe, not subscribed'
- );
- expect(shallowRender({ canSubscribe: true, subscribed: true })).toMatchSnapshot(
- 'can subscribe, subscribed'
- );
- expect(shallowRender({ canSubscribe: true, currentUserHasEmail: false })).toMatchSnapshot(
- 'current user without email'
- );
- expect(shallowRender({ component: mockComponent() })).toMatchSnapshot('not a portfolio');
-});
-
-function shallowRender(props: Partial<ComponentReportActionsRendererProps> = {}) {
- return shallow<ComponentReportActionsRendererProps>(
- <ComponentReportActionsRenderer
- component={mockComponent({ qualifier: ComponentQualifier.Portfolio })}
- canSubscribe={true}
- subscribed={false}
- currentUserHasEmail={true}
- frequency="weekly"
- handleSubscription={jest.fn()}
- handleUnsubscription={jest.fn()}
- {...props}
- />
- );
-}
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: can subscribe, not subscribed 1`] = `
-<Dropdown
- overlay={
- <ul
- className="menu"
- >
- <li>
- <a
- download="MyProject - PDF Report.pdf"
- href="/api/governance_reports/download?componentKey=my-project"
- rel="noopener noreferrer"
- target="_blank"
- >
- download_verb
- </a>
- </li>
- <li>
- <a
- data-test="overview__subscribe-to-report-button"
- href="#"
- onClick={[MockFunction]}
- >
- component_report.subscribe_x.report.frequency.weekly
- </a>
- </li>
- </ul>
- }
->
- <Button
- className="dropdown-toggle"
- >
- component_report.report.qualifier.VW
- <DropdownIcon
- className="spacer-left"
- />
- </Button>
-</Dropdown>
-`;
-
-exports[`should render correctly: can subscribe, subscribed 1`] = `
-<Dropdown
- overlay={
- <ul
- className="menu"
- >
- <li>
- <a
- download="MyProject - PDF Report.pdf"
- href="/api/governance_reports/download?componentKey=my-project"
- rel="noopener noreferrer"
- target="_blank"
- >
- download_verb
- </a>
- </li>
- <li>
- <a
- data-test="overview__subscribe-to-report-button"
- href="#"
- onClick={[MockFunction]}
- >
- component_report.unsubscribe_x.report.frequency.weekly
- </a>
- </li>
- </ul>
- }
->
- <Button
- className="dropdown-toggle"
- >
- component_report.report.qualifier.VW
- <DropdownIcon
- className="spacer-left"
- />
- </Button>
-</Dropdown>
-`;
-
-exports[`should render correctly: cannot subscribe 1`] = `
-<a
- download="MyProject - PDF Report.pdf"
- href="/api/governance_reports/download?componentKey=my-project"
- rel="noopener noreferrer"
- target="_blank"
->
- component_report.download.qualifier.vw
-</a>
-`;
-
-exports[`should render correctly: current user without email 1`] = `
-<Dropdown
- overlay={
- <ul
- className="menu"
- >
- <li>
- <a
- download="MyProject - PDF Report.pdf"
- href="/api/governance_reports/download?componentKey=my-project"
- rel="noopener noreferrer"
- target="_blank"
- >
- download_verb
- </a>
- </li>
- <li>
- <span
- className="text-muted-2"
- >
- component_report.no_email_to_subscribe
- </span>
- </li>
- </ul>
- }
->
- <Button
- className="dropdown-toggle"
- >
- component_report.report.qualifier.VW
- <DropdownIcon
- className="spacer-left"
- />
- </Button>
-</Dropdown>
-`;
-
-exports[`should render correctly: not a portfolio 1`] = `
-<Dropdown
- overlay={
- <ul
- className="menu"
- >
- <li>
- <a
- download="MyProject - PDF Report.pdf"
- href="/api/governance_reports/download?componentKey=my-project"
- rel="noopener noreferrer"
- target="_blank"
- >
- download_verb
- </a>
- </li>
- <li>
- <a
- data-test="overview__subscribe-to-report-button"
- href="#"
- onClick={[MockFunction]}
- >
- component_report.subscribe_x.report.frequency.weekly
- </a>
- </li>
- </ul>
- }
->
- <Button
- className="dropdown-toggle"
- >
- component_report.report.qualifier.TRK
- <DropdownIcon
- className="spacer-left"
- />
- </Button>
-</Dropdown>
-`;