diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-12-12 10:39:12 +0100 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-12-12 20:21:03 +0100 |
commit | 7bba1341b905e96f9b85e965226b39ddc787dee2 (patch) | |
tree | 7ed409d7aa684082d290eb5bb53a41e73b8de636 | |
parent | c1caffa9fb8cabe9659d81372d82481edc00b545 (diff) | |
download | sonarqube-7bba1341b905e96f9b85e965226b39ddc787dee2.tar.gz sonarqube-7bba1341b905e96f9b85e965226b39ddc787dee2.zip |
finish removing legacy react context (#1055)
50 files changed, 364 insertions, 383 deletions
diff --git a/server/sonar-web/src/main/js/app/components/App.tsx b/server/sonar-web/src/main/js/app/components/App.tsx index d3f99a4985b..c4c50beb7ee 100644 --- a/server/sonar-web/src/main/js/app/components/App.tsx +++ b/server/sonar-web/src/main/js/app/components/App.tsx @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import { connect } from 'react-redux'; import Helmet from 'react-helmet'; import { fetchLanguages } from '../../store/rootActions'; @@ -51,21 +50,6 @@ type Props = StateProps & DispatchProps & OwnProps; class App extends React.PureComponent<Props> { mounted = false; - static childContextTypes = { - branchesEnabled: PropTypes.bool.isRequired, - canAdmin: PropTypes.bool.isRequired, - organizationsEnabled: PropTypes.bool - }; - - getChildContext() { - const { appState } = this.props; - return { - branchesEnabled: (appState && appState.branchesEnabled) || false, - canAdmin: (appState && appState.canAdmin) || false, - organizationsEnabled: (appState && appState.organizationsEnabled) || false - }; - } - componentDidMount() { this.mounted = true; this.props.fetchLanguages(); diff --git a/server/sonar-web/src/main/js/app/components/OnboardingContext.tsx b/server/sonar-web/src/main/js/app/components/OnboardingContext.tsx new file mode 100644 index 00000000000..9b8d000a9d3 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/OnboardingContext.tsx @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { createContext } from 'react'; + +export type OnboardingContextShape = (organization?: T.Organization) => void; + +export const OnboardingContext = createContext<OnboardingContextShape>(() => {}); diff --git a/server/sonar-web/src/main/js/app/components/StartupModal.tsx b/server/sonar-web/src/main/js/app/components/StartupModal.tsx index c26d26f032a..8d70aa9d802 100644 --- a/server/sonar-web/src/main/js/app/components/StartupModal.tsx +++ b/server/sonar-web/src/main/js/app/components/StartupModal.tsx @@ -18,9 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { withRouter, WithRouterProps } from 'react-router'; +import { OnboardingContext } from './OnboardingContext'; import { differenceInDays, parseDate, toShortNotSoISOString } from '../../helpers/dates'; import { getCurrentUser, getAppState, Store } from '../../store/rootReducer'; import { skipOnboarding } from '../../store/users'; @@ -75,16 +75,8 @@ interface State { const LICENSE_PROMPT = 'sonarqube.license.prompt'; export class StartupModal extends React.PureComponent<Props, State> { - static childContextTypes = { - openProjectOnboarding: PropTypes.func - }; - state: State = { automatic: false }; - getChildContext() { - return { openProjectOnboarding: this.openProjectOnboarding }; - } - componentDidMount() { this.tryAutoOpenLicense().catch(this.tryAutoOpenOnboarding); } @@ -172,7 +164,7 @@ export class StartupModal extends React.PureComponent<Props, State> { render() { const { automatic, modal } = this.state; return ( - <> + <OnboardingContext.Provider value={this.openProjectOnboarding}> {this.props.children} {modal === ModalKey.license && <LicensePromptModal onClose={this.closeLicense} />} {modal === ModalKey.onboarding && ( @@ -188,7 +180,7 @@ export class StartupModal extends React.PureComponent<Props, State> { {modal === ModalKey.teamOnboarding && ( <TeamOnboardingModal onFinish={this.closeOnboarding} /> )} - </> + </OnboardingContext.Provider> ); } } diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx index a136ce19518..ef1d79fe8ce 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import { connect } from 'react-redux'; import GlobalNavBranding, { SonarCloudNavBranding } from './GlobalNavBranding'; import GlobalNavMenu from './GlobalNavMenu'; @@ -32,6 +31,7 @@ import { lazyLoad } from '../../../../components/lazyLoad'; import { getCurrentUser, getAppState, Store } from '../../../../store/rootReducer'; import { isSonarCloud } from '../../../../helpers/system'; import { isLoggedIn } from '../../../../helpers/users'; +import { OnboardingContext } from '../../OnboardingContext'; import './GlobalNav.css'; const GlobalNavPlus = lazyLoad(() => import('./GlobalNavPlus'), 'GlobalNavPlus'); @@ -48,8 +48,6 @@ interface OwnProps { type Props = StateProps & OwnProps; export class GlobalNav extends React.PureComponent<Props> { - static contextTypes = { openProjectOnboarding: PropTypes.func }; - render() { const { appState, currentUser } = this.props; return ( @@ -63,11 +61,15 @@ export class GlobalNav extends React.PureComponent<Props> { <EmbedDocsPopupHelper /> <Search appState={appState} currentUser={currentUser} /> {isLoggedIn(currentUser) && ( - <GlobalNavPlus - appState={appState} - currentUser={currentUser} - openProjectOnboarding={this.context.openProjectOnboarding} - /> + <OnboardingContext.Consumer data-test="global-nav-plus"> + {openProjectOnboarding => ( + <GlobalNavPlus + appState={appState} + currentUser={currentUser} + openProjectOnboarding={openProjectOnboarding} + /> + )} + </OnboardingContext.Consumer> )} <GlobalNavUserContainer appState={appState} currentUser={currentUser} /> </ul> diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx index e090986643f..336d8734b38 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx @@ -28,11 +28,12 @@ import { translate } from '../../../../helpers/l10n'; import { isSonarCloud } from '../../../../helpers/system'; import { getPortfolioAdminUrl, getPortfolioUrl } from '../../../../helpers/urls'; import { hasGlobalPermission } from '../../../../helpers/users'; +import { OnboardingContextShape } from '../../OnboardingContext'; interface Props { appState: Pick<T.AppState, 'qualifiers'>; currentUser: T.LoggedInUser; - openProjectOnboarding: () => void; + openProjectOnboarding: OnboardingContextShape; } interface State { diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx index 1656f723eda..edcf7972084 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx @@ -47,5 +47,5 @@ function runTest(mockedIsSonarCloud: boolean) { ); expect(wrapper).toMatchSnapshot(); wrapper.setProps({ currentUser: { isLoggedIn: true } }); - expect(wrapper.find('GlobalNavPlus').exists()).toBe(true); + expect(wrapper.find('[data-test="global-nav-plus"]').exists()).toBe(true); } diff --git a/server/sonar-web/src/main/js/apps/code/components/Component.tsx b/server/sonar-web/src/main/js/apps/code/components/Component.tsx index 9352241bd26..3e3dc6e6e3c 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Component.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Component.tsx @@ -23,6 +23,7 @@ import ComponentName from './ComponentName'; import ComponentMeasure from './ComponentMeasure'; import ComponentLink from './ComponentLink'; import ComponentPin from './ComponentPin'; +import { WorkspaceContext } from '../../../components/workspace/context'; const TOP_OFFSET = 200; const BOTTOM_OFFSET = 10; @@ -90,7 +91,17 @@ export default class Component extends React.PureComponent<Props> { switch (component.qualifier) { case 'FIL': case 'UTS': - componentAction = <ComponentPin branchLike={branchLike} component={component} />; + componentAction = ( + <WorkspaceContext.Consumer> + {({ openComponent }) => ( + <ComponentPin + branchLike={branchLike} + component={component} + openComponent={openComponent} + /> + )} + </WorkspaceContext.Consumer> + ); break; default: componentAction = <ComponentLink branchLike={branchLike} component={component} />; diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx index f486ce21bf3..424c0d0f7b0 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx @@ -18,27 +18,21 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import PinIcon from '../../../components/icons-components/PinIcon'; -import { WorkspaceContext } from '../../../components/workspace/context'; +import { WorkspaceContextShape } from '../../../components/workspace/context'; import { translate } from '../../../helpers/l10n'; interface Props { branchLike?: T.BranchLike; component: T.ComponentMeasure; + openComponent: WorkspaceContextShape['openComponent']; } export default class ComponentPin extends React.PureComponent<Props> { - context!: { workspace: WorkspaceContext }; - - static contextTypes = { - workspace: PropTypes.object.isRequired - }; - handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { event.preventDefault(); event.currentTarget.blur(); - this.context.workspace.openComponent({ + this.props.openComponent({ branchLike: this.props.branchLike, key: this.props.component.key, name: this.props.component.path, diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/AvailableSinceFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/AvailableSinceFacet.tsx index 7bc746a447b..ebe5996c7e2 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/AvailableSinceFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/AvailableSinceFacet.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { intlShape } from 'react-intl'; +import { injectIntl, InjectedIntlProps } from 'react-intl'; import { Query } from '../query'; import DateInput from '../../../components/controls/DateInput'; import FacetBox from '../../../components/facet/FacetBox'; @@ -33,14 +33,14 @@ interface Props { value?: Date; } -export default class AvailableSinceFacet extends React.PureComponent<Props> { - static contextTypes = { - intl: intlShape +class AvailableSinceFacet extends React.PureComponent<Props & InjectedIntlProps> { + handleHeaderClick = () => { + this.props.onToggle('availableSince'); }; - handleHeaderClick = () => this.props.onToggle('availableSince'); - - handleClear = () => this.props.onChange({ availableSince: undefined }); + handleClear = () => { + this.props.onChange({ availableSince: undefined }); + }; handlePeriodChange = (date: Date | undefined) => { this.props.onChange({ availableSince: date }); @@ -48,7 +48,7 @@ export default class AvailableSinceFacet extends React.PureComponent<Props> { getValues = () => this.props.value - ? [this.context.intl.formatDate(this.props.value, longFormatterOption)] + ? [this.props.intl.formatDate(this.props.value, longFormatterOption)] : undefined; render() { @@ -74,3 +74,5 @@ export default class AvailableSinceFacet extends React.PureComponent<Props> { ); } } + +export default injectIntl(AvailableSinceFacet); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx index 9f145e734af..41c3829f368 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import * as classNames from 'classnames'; -import * as PropTypes from 'prop-types'; +import { injectIntl, InjectedIntlProps } from 'react-intl'; import DateFromNow from '../../../components/intl/DateFromNow'; import DateFormatter, { longFormatterOption } from '../../../components/intl/DateFormatter'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; @@ -34,13 +34,9 @@ interface Props { period: T.Period; } -export default class LeakPeriodLegend extends React.PureComponent<Props> { - static contextTypes = { - intl: PropTypes.object.isRequired - }; - +export class LeakPeriodLegend extends React.PureComponent<Props & InjectedIntlProps> { formatDate = (date: string) => { - return this.context.intl.formatDate(date, longFormatterOption); + return this.props.intl.formatDate(date, longFormatterOption); }; render() { @@ -81,3 +77,5 @@ export default class LeakPeriodLegend extends React.PureComponent<Props> { return <Tooltip overlay={tooltip}>{label}</Tooltip>; } } + +export default injectIntl(LeakPeriodLegend); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.tsx index 3aa1046f6dc..bbb14e014d8 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.tsx @@ -19,7 +19,8 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import LeakPeriodLegend from '../LeakPeriodLegend'; +import { InjectedIntlProps } from 'react-intl'; +import { LeakPeriodLegend } from '../LeakPeriodLegend'; import { differenceInDays } from '../../../../helpers/dates'; jest.mock('../../../../helpers/dates', () => { @@ -69,9 +70,11 @@ it('should render a more precise date', () => { }); function getWrapper(component: T.ComponentMeasure, period: T.Period) { - return shallow(<LeakPeriodLegend component={component} period={period} />, { - context: { - intl: { formatDate: (date: string) => 'formatted.' + date } - } - }); + return shallow( + <LeakPeriodLegend + component={component} + intl={{ formatDate: (x: any) => x } as InjectedIntlProps['intl']} + period={period} + /> + ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap index 7a4677c1b7c..7de2ed12e3b 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap @@ -51,7 +51,7 @@ exports[`should render correctly 1`] = ` <div className="measure-details-primary-actions" > - <LeakPeriodLegend + <InjectIntl(LeakPeriodLegend) className="spacer-left" component={ Object { @@ -105,7 +105,7 @@ exports[`should render correctly for leak 1`] = ` <div className="measure-details-primary-actions" > - <LeakPeriodLegend + <InjectIntl(LeakPeriodLegend) className="spacer-left" component={ Object { @@ -234,7 +234,7 @@ exports[`should work with measure without value 1`] = ` <div className="measure-details-primary-actions" > - <LeakPeriodLegend + <InjectIntl(LeakPeriodLegend) className="spacer-left" component={ Object { diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx index ff574566f0d..85980a6ec2b 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx @@ -18,10 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { shallow } from 'enzyme'; import { App } from '../App'; -import { shallowWithIntl, waitAndUpdate } from '../../../../helpers/testUtils'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; -const replace = jest.fn(); const issues = [ { key: 'foo' } as T.Issue, { key: 'bar' } as T.Issue, @@ -65,10 +65,7 @@ const PROPS = { }; it('should render a list of issue', async () => { - const wrapper = shallowWithIntl<App>(<App {...PROPS} />, { - context: { router: { replace } } - }); - + const wrapper = shallow<App>(<App {...PROPS} />); await waitAndUpdate(wrapper); expect(wrapper.state().issues.length).toBe(4); expect(wrapper.state().referencedComponentsById).toEqual({ 'foo-uuid': referencedComponent }); @@ -76,10 +73,7 @@ it('should render a list of issue', async () => { }); it('should be able to check/uncheck a group of issues with the Shift key', async () => { - const wrapper = shallowWithIntl<App>(<App {...PROPS} />, { - context: { router: { replace } } - }); - + const wrapper = shallow<App>(<App {...PROPS} />); await waitAndUpdate(wrapper); expect(wrapper.state().issues.length).toBe(4); @@ -98,10 +92,7 @@ it('should be able to check/uncheck a group of issues with the Shift key', async }); it('should avoid non-existing keys', async () => { - const wrapper = shallowWithIntl<App>(<App {...PROPS} />, { - context: { router: { replace } } - }); - + const wrapper = shallow<App>(<App {...PROPS} />); await waitAndUpdate(wrapper); expect(wrapper.state().issues.length).toBe(4); @@ -114,10 +105,7 @@ it('should avoid non-existing keys', async () => { }); it('should be able to uncheck all issue with global checkbox', async () => { - const wrapper = shallowWithIntl<App>(<App {...PROPS} />, { - context: { router: { replace } } - }); - + const wrapper = shallow<App>(<App {...PROPS} />); await waitAndUpdate(wrapper); expect(wrapper.state().issues.length).toBe(4); @@ -131,10 +119,7 @@ it('should be able to uncheck all issue with global checkbox', async () => { }); it('should be able to check all issue with global checkbox', async () => { - const wrapper = shallowWithIntl<App>(<App {...PROPS} />, { - context: { router: { replace } } - }); - + const wrapper = shallow<App>(<App {...PROPS} />); await waitAndUpdate(wrapper); const instance = wrapper.instance(); diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx index 240d69eaf98..5a92120c88d 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { max } from 'lodash'; -import { intlShape } from 'react-intl'; +import { injectIntl, InjectedIntlProps } from 'react-intl'; import { Query } from '../utils'; import FacetBox from '../../../components/facet/FacetBox'; import FacetHeader from '../../../components/facet/FacetHeader'; @@ -48,17 +48,13 @@ interface Props { stats: { [x: string]: number } | undefined; } -export default class CreationDateFacet extends React.PureComponent<Props> { +class CreationDateFacet extends React.PureComponent<Props & InjectedIntlProps> { property = 'createdAt'; static defaultProps = { open: true }; - static contextTypes = { - intl: intlShape - }; - hasValue = () => this.props.createdAfter !== undefined || this.props.createdAt.length > 0 || @@ -105,7 +101,7 @@ export default class CreationDateFacet extends React.PureComponent<Props> { getValues() { const { createdAfter, createdAt, createdBefore, createdInLast, sinceLeakPeriod } = this.props; - const { formatDate } = this.context.intl; + const { formatDate } = this.props.intl; const values = []; if (createdAfter) { values.push(formatDate(createdAfter, longFormatterOption)); @@ -144,7 +140,7 @@ export default class CreationDateFacet extends React.PureComponent<Props> { return null; } - const { formatDate } = this.context.intl; + const { formatDate } = this.props.intl; const data = periods.map((start, index) => { const startDate = parseDate(start); let endDate; @@ -296,3 +292,5 @@ export default class CreationDateFacet extends React.PureComponent<Props> { ); } } + +export default injectIntl(CreationDateFacet); diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap index bc2e66a808c..d12f3038760 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap @@ -6,7 +6,7 @@ Array [ "SeverityFacet", "ResolutionFacet", "StatusFacet", - "CreationDateFacet", + "InjectIntl(CreationDateFacet)", "Connect(LanguageFacet)", "RuleFacet", "StandardFacet", @@ -25,7 +25,7 @@ Array [ "SeverityFacet", "ResolutionFacet", "StatusFacet", - "CreationDateFacet", + "InjectIntl(CreationDateFacet)", "Connect(LanguageFacet)", "RuleFacet", "StandardFacet", @@ -42,7 +42,7 @@ Array [ "SeverityFacet", "ResolutionFacet", "StatusFacet", - "CreationDateFacet", + "InjectIntl(CreationDateFacet)", "Connect(LanguageFacet)", "RuleFacet", "StandardFacet", @@ -59,7 +59,7 @@ Array [ "SeverityFacet", "ResolutionFacet", "StatusFacet", - "CreationDateFacet", + "InjectIntl(CreationDateFacet)", "Connect(LanguageFacet)", "RuleFacet", "StandardFacet", @@ -78,7 +78,7 @@ Array [ "SeverityFacet", "ResolutionFacet", "StatusFacet", - "CreationDateFacet", + "InjectIntl(CreationDateFacet)", "Connect(LanguageFacet)", "RuleFacet", "StandardFacet", @@ -97,7 +97,7 @@ Array [ "SeverityFacet", "ResolutionFacet", "StatusFacet", - "CreationDateFacet", + "InjectIntl(CreationDateFacet)", "Connect(LanguageFacet)", "RuleFacet", "StandardFacet", diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.tsx index 23751fc6dc4..89f1d217de5 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.tsx @@ -18,25 +18,24 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { withRouter, WithRouterProps } from 'react-router'; import { Button } from '../../../components/ui/buttons'; import OnboardingProjectIcon from '../../../components/icons-components/OnboardingProjectIcon'; import OnboardingAddMembersIcon from '../../../components/icons-components/OnboardingAddMembersIcon'; import { translate } from '../../../helpers/l10n'; +import { OnboardingContextShape } from '../../../app/components/OnboardingContext'; +import { withRouter, Router } from '../../../components/hoc/withRouter'; import '../../tutorials/styles.css'; import './OrganizationJustCreated.css'; interface Props { + openProjectOnboarding: OnboardingContextShape; organization: T.Organization; + router: Pick<Router, 'push'>; } -export class OrganizationJustCreated extends React.PureComponent<Props & WithRouterProps> { - static contextTypes = { - openProjectOnboarding: () => null - }; - +export class OrganizationJustCreated extends React.PureComponent<Props> { handleNewProjectClick = () => { - this.context.openProjectOnboarding(this.props.organization); + this.props.openProjectOnboarding(this.props.organization); }; handleAddMembersClick = () => { diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx index f0d281877eb..d8770331308 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx @@ -32,6 +32,7 @@ import { getMyOrganizations, Store } from '../../../store/rootReducer'; +import { OnboardingContext } from '../../../app/components/OnboardingContext'; interface OwnProps { children?: React.ReactNode; @@ -89,7 +90,14 @@ export class OrganizationPage extends React.PureComponent<Props, State> { const { location } = this.props; const justCreated = Boolean(location.state && location.state.justCreated); return justCreated ? ( - <OrganizationJustCreated organization={organization} /> + <OnboardingContext.Consumer> + {openProjectOnboarding => ( + <OrganizationJustCreated + openProjectOnboarding={openProjectOnboarding} + organization={organization} + /> + )} + </OnboardingContext.Consumer> ) : ( this.props.children ); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationJustCreated-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationJustCreated-test.tsx index 08ee42d00db..0bcc3631ed5 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationJustCreated-test.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationJustCreated-test.tsx @@ -25,24 +25,39 @@ import { click } from '../../../../helpers/testUtils'; const organization: T.Organization = { key: 'foo', name: 'Foo' }; it('should render', () => { - // @ts-ignore - expect(shallow(<OrganizationJustCreated organization={organization} />)).toMatchSnapshot(); + expect( + shallow( + <OrganizationJustCreated + openProjectOnboarding={jest.fn()} + organization={organization} + router={{ push: jest.fn() }} + /> + ) + ).toMatchSnapshot(); }); it('should create new project', () => { const openProjectOnboarding = jest.fn(); - // @ts-ignore - const wrapper = shallow(<OrganizationJustCreated organization={organization} />, { - context: { openProjectOnboarding } - }); + const wrapper = shallow( + <OrganizationJustCreated + openProjectOnboarding={openProjectOnboarding} + organization={organization} + router={{ push: jest.fn() }} + /> + ); click(wrapper.find('Button').first()); expect(openProjectOnboarding).toBeCalledWith({ key: 'foo', name: 'Foo' }); }); it('should add members', () => { const router = { push: jest.fn() }; - // @ts-ignore - const wrapper = shallow(<OrganizationJustCreated organization={organization} router={router} />); + const wrapper = shallow( + <OrganizationJustCreated + openProjectOnboarding={jest.fn()} + organization={organization} + router={router} + /> + ); click(wrapper.find('Button').last()); expect(router.push).toBeCalledWith('/organizations/foo/members'); }); diff --git a/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx b/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx index fd3615653aa..28e94e67a51 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as PropTypes from 'prop-types'; +import { injectIntl, InjectedIntlProps } from 'react-intl'; import DateFromNow from '../../../components/intl/DateFromNow'; import DateFormatter, { longFormatterOption } from '../../../components/intl/DateFormatter'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; @@ -31,13 +31,9 @@ interface Props { period: T.Period; } -export default class LeakPeriodLegend extends React.PureComponent<Props> { - static contextTypes = { - intl: PropTypes.object.isRequired - }; - +export class LeakPeriodLegend extends React.PureComponent<Props & InjectedIntlProps> { formatDate = (date: string) => { - return this.context.intl.formatDate(date, longFormatterOption); + return this.props.intl.formatDate(date, longFormatterOption); }; render() { @@ -102,3 +98,5 @@ export default class LeakPeriodLegend extends React.PureComponent<Props> { ); } } + +export default injectIntl(LeakPeriodLegend); diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx index f4a39fd5f13..48efcd8ce5d 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx @@ -18,8 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { InjectedIntlProps } from 'react-intl'; import { shallow } from 'enzyme'; -import LeakPeriodLegend from '../LeakPeriodLegend'; +import { LeakPeriodLegend } from '../LeakPeriodLegend'; import { differenceInDays } from '../../../../helpers/dates'; jest.mock('../../../../helpers/dates', () => { @@ -93,9 +94,10 @@ it('should render a more precise date', () => { }); function getWrapper(period: T.Period) { - return shallow(<LeakPeriodLegend period={period} />, { - context: { - intl: { formatDate: (date: string) => 'formatted.' + date } - } - }); + return shallow( + <LeakPeriodLegend + intl={{ formatDate: (date: string) => 'formatted.' + date } as InjectedIntlProps['intl']} + period={period} + /> + ); } diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityDateInput-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityDateInput-test.tsx index fa65ca538d0..c2dacd4fe90 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityDateInput-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityDateInput-test.tsx @@ -18,13 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { shallowWithIntl } from '../../../../helpers/testUtils'; +import { shallow } from 'enzyme'; import ProjectActivityDateInput from '../ProjectActivityDateInput'; import { parseDate } from '../../../../helpers/dates'; it('should render correctly the date inputs', () => { expect( - shallowWithIntl( + shallow( <ProjectActivityDateInput from={parseDate('2016-10-27T12:21:15+0000')} onChange={() => {}} diff --git a/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx b/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx index 790e0970db3..4ef97f6d99f 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx @@ -18,24 +18,21 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import { translate } from '../../../helpers/l10n'; import { Button } from '../../../components/ui/buttons'; import { isSonarCloud } from '../../../helpers/system'; import { hasGlobalPermission, isLoggedIn } from '../../../helpers/users'; +import { OnboardingContextShape } from '../../../app/components/OnboardingContext'; interface Props { - organization?: T.Organization; currentUser: T.CurrentUser; + openProjectOnboarding: OnboardingContextShape; + organization?: T.Organization; } export default class EmptyInstance extends React.PureComponent<Props> { - static contextTypes = { - openProjectOnboarding: PropTypes.func - }; - analyzeNewProject = () => { - this.context.openProjectOnboarding(this.props.organization); + this.props.openProjectOnboarding(this.props.organization); }; render() { diff --git a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx index c28dc6312b7..f192a900b14 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx @@ -20,7 +20,6 @@ import * as React from 'react'; import { Link } from 'react-router'; import { connect } from 'react-redux'; -import * as PropTypes from 'prop-types'; import { sortBy } from 'lodash'; import DropdownIcon from '../../../components/icons-components/DropdownIcon'; import Dropdown from '../../../components/controls/Dropdown'; @@ -29,67 +28,59 @@ import { Button } from '../../../components/ui/buttons'; import { getMyOrganizations, Store } from '../../../store/rootReducer'; import { isSonarCloud } from '../../../helpers/system'; import { translate } from '../../../helpers/l10n'; +import { OnboardingContextShape } from '../../../app/components/OnboardingContext'; + +interface OwnProps { + openProjectOnboarding: OnboardingContextShape; +} interface StateProps { organizations: T.Organization[]; } -export class NoFavoriteProjects extends React.PureComponent<StateProps> { - static contextTypes = { - openProjectOnboarding: PropTypes.func - }; - - onAnalyzeProjectClick = () => { - this.context.openProjectOnboarding(); - }; +export function NoFavoriteProjects(props: StateProps & OwnProps) { + return ( + <div className="projects-empty-list"> + <h3>{translate('projects.no_favorite_projects')}</h3> + {isSonarCloud() ? ( + <div className="spacer-top"> + <p>{translate('projects.no_favorite_projects.how_to_add_projects')}</p> + <div className="huge-spacer-top"> + <Button onClick={props.openProjectOnboarding}> + {translate('provisioning.analyze_new_project')} + </Button> - render() { - const { organizations } = this.props; - return ( - <div className="projects-empty-list"> - <h3>{translate('projects.no_favorite_projects')}</h3> - {isSonarCloud() ? ( - <div className="spacer-top"> - <p>{translate('projects.no_favorite_projects.how_to_add_projects')}</p> - <div className="huge-spacer-top"> - <Button onClick={this.onAnalyzeProjectClick}> - {translate('provisioning.analyze_new_project')} - </Button> - - <Dropdown - className="display-inline-block big-spacer-left" - overlay={ - <ul className="menu"> - {sortBy(organizations, org => org.name.toLowerCase()).map(organization => ( - <OrganizationListItem key={organization.key} organization={organization} /> - ))} - </ul> - }> - <a className="button" href="#"> - {translate('projects.no_favorite_projects.favorite_projects_from_orgs')} - <DropdownIcon className="little-spacer-left" /> - </a> - </Dropdown> - <Link className="button big-spacer-left" to="/explore/projects"> - {translate('projects.no_favorite_projects.favorite_public_projects')} - </Link> - </div> - </div> - ) : ( - <div> - <p className="big-spacer-top"> - {translate('projects.no_favorite_projects.engagement')} - </p> - <p className="big-spacer-top"> - <Link className="button" to="/projects/all"> - {translate('projects.explore_projects')} - </Link> - </p> + <Dropdown + className="display-inline-block big-spacer-left" + overlay={ + <ul className="menu"> + {sortBy(props.organizations, org => org.name.toLowerCase()).map(organization => ( + <OrganizationListItem key={organization.key} organization={organization} /> + ))} + </ul> + }> + <a className="button" href="#"> + {translate('projects.no_favorite_projects.favorite_projects_from_orgs')} + <DropdownIcon className="little-spacer-left" /> + </a> + </Dropdown> + <Link className="button big-spacer-left" to="/explore/projects"> + {translate('projects.no_favorite_projects.favorite_public_projects')} + </Link> </div> - )} - </div> - ); - } + </div> + ) : ( + <div> + <p className="big-spacer-top">{translate('projects.no_favorite_projects.engagement')}</p> + <p className="big-spacer-top"> + <Link className="button" to="/projects/all"> + {translate('projects.explore_projects')} + </Link> + </p> + </div> + )} + </div> + ); } const mapStateToProps = (state: Store): StateProps => ({ diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx index 8bb2f535070..f54e32f95d2 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx @@ -28,6 +28,7 @@ import EmptyFavoriteSearch from './EmptyFavoriteSearch'; import EmptySearch from '../../../components/common/EmptySearch'; import { Project } from '../types'; import { Query } from '../query'; +import { OnboardingContext } from '../../../app/components/OnboardingContext'; interface Props { cardType?: string; @@ -50,9 +51,21 @@ export default class ProjectsList extends React.PureComponent<Props> { return isFavorite ? <EmptyFavoriteSearch query={query} /> : <EmptySearch />; } return isFavorite ? ( - <NoFavoriteProjects /> + <OnboardingContext.Consumer> + {openProjectOnboarding => ( + <NoFavoriteProjects openProjectOnboarding={openProjectOnboarding} /> + )} + </OnboardingContext.Consumer> ) : ( - <EmptyInstance currentUser={currentUser} organization={organization} /> + <OnboardingContext.Consumer> + {openProjectOnboarding => ( + <EmptyInstance + currentUser={currentUser} + openProjectOnboarding={openProjectOnboarding} + organization={organization} + /> + )} + </OnboardingContext.Consumer> ); } diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx index 6cc4deb62f0..667a36abe8a 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx @@ -29,12 +29,19 @@ jest.mock('../../../../helpers/system', () => ({ it('renders correctly for SQ', () => { (isSonarCloud as jest.Mock<any>).mockReturnValue(false); expect( - shallow(<EmptyInstance currentUser={{ isLoggedIn: false }} organization={undefined} />) + shallow( + <EmptyInstance + currentUser={{ isLoggedIn: false }} + openProjectOnboarding={jest.fn()} + organization={undefined} + /> + ) ).toMatchSnapshot(); expect( shallow( <EmptyInstance currentUser={{ isLoggedIn: true, permissions: { global: ['provisioning'] } }} + openProjectOnboarding={jest.fn()} organization={undefined} /> ) @@ -47,6 +54,7 @@ it('renders correctly for SC', () => { shallow( <EmptyInstance currentUser={{ isLoggedIn: false }} + openProjectOnboarding={jest.fn()} organization={{ key: 'foo', name: 'Foo' }} /> ) @@ -55,6 +63,7 @@ it('renders correctly for SC', () => { shallow( <EmptyInstance currentUser={{ isLoggedIn: false }} + openProjectOnboarding={jest.fn()} organization={{ actions: { provision: true }, key: 'foo', name: 'Foo' }} /> ) diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx index 0e0f2af3a93..35804e7f382 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx @@ -26,7 +26,9 @@ jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() })); it('renders', () => { (isSonarCloud as jest.Mock).mockImplementation(() => false); - expect(shallow(<NoFavoriteProjects organizations={[]} />)).toMatchSnapshot(); + expect( + shallow(<NoFavoriteProjects openProjectOnboarding={jest.fn()} organizations={[]} />) + ).toMatchSnapshot(); }); it('renders for SonarCloud', () => { @@ -35,5 +37,7 @@ it('renders for SonarCloud', () => { { actions: { admin: true }, key: 'org1', name: 'org1', projectVisibility: 'public' }, { actions: { admin: false }, key: 'org2', name: 'org2', projectVisibility: 'public' } ]; - expect(shallow(<NoFavoriteProjects organizations={organizations} />)).toMatchSnapshot(); + expect( + shallow(<NoFavoriteProjects openProjectOnboarding={jest.fn()} organizations={organizations} />) + ).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap index 5b57d76dec3..d1b0a82b9c6 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap @@ -46,7 +46,7 @@ exports[`renders for SonarCloud 1`] = ` className="huge-spacer-top" > <Button - onClick={[Function]} + onClick={[MockFunction]} > provisioning.analyze_new_project </Button> diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap index bc326ba6477..14bf2862347 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap @@ -21,13 +21,9 @@ exports[`renders different types of "no projects" 1`] = ` <div className="projects-list" > - <EmptyInstance - currentUser={ - Object { - "isLoggedIn": true, - } - } - /> + <ContextConsumer> + <Component /> + </ContextConsumer> </div> `; @@ -43,6 +39,8 @@ exports[`renders different types of "no projects" 3`] = ` <div className="projects-list" > - <Connect(NoFavoriteProjects) /> + <ContextConsumer> + <Component /> + </ContextConsumer> </div> `; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilterContainer-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilterContainer-test.tsx index ca70676d485..3af18a5a408 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilterContainer-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilterContainer-test.tsx @@ -23,9 +23,7 @@ import SearchFilterContainer from '../SearchFilterContainer'; it('searches', () => { const onQueryChange = jest.fn(); - const wrapper = shallow(<SearchFilterContainer onQueryChange={onQueryChange} query={{}} />, { - context: { router: { push: jest.fn() } } - }); + const wrapper = shallow(<SearchFilterContainer onQueryChange={onQueryChange} query={{}} />); expect(wrapper).toMatchSnapshot(); wrapper.find('SearchBox').prop<Function>('onChange')('foo'); expect(onQueryChange).toBeCalledWith({ search: 'foo' }); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.tsx index 4bc227b9949..25f2ecddd41 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.tsx @@ -34,8 +34,7 @@ it('should render items without the ones in the facet', () => { options={options} property="languages" query={{ languages: ['java'] }} - />, - { context: { router: { push: jest.fn() } } } + /> ); expect(wrapper.find('Select').prop('options')).toMatchSnapshot(); }); @@ -48,8 +47,7 @@ it('should render items without the ones in the facet', () => { options={options} property="languages" query={{ languages: ['java'] }} - />, - { context: { router: { push: jest.fn() } } } + /> ); (wrapper.find('Select').prop('onChange') as Function)({ value: 'js' }); expect(onQueryChange).toBeCalledWith({ languages: 'java,js' }); diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx index 983786ae8df..162ce640974 100644 --- a/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx @@ -85,7 +85,6 @@ const getSecurityHotspots = require('../../../../api/security-reports') .getSecurityHotspots as jest.Mock<any>; const component = { key: 'foo', name: 'Foo', qualifier: 'TRK' } as T.Component; -const context = { router: { push: jest.fn() } }; const location = { pathname: 'foo', query: {} }; const locationWithCWE = { pathname: 'foo', query: { showCWE: 'true' } }; const owaspParams = { type: 'owasp_top_10' }; @@ -103,10 +102,7 @@ it('renders error on wrong type parameters', () => { location={location} params={wrongParams} router={{ push: jest.fn() }} - />, - { - context - } + /> ); expect(wrapper).toMatchSnapshot(); }); @@ -118,10 +114,7 @@ it('renders owaspTop10', async () => { location={location} params={owaspParams} router={{ push: jest.fn() }} - />, - { - context - } + /> ); await waitAndUpdate(wrapper); expect(getSecurityHotspots).toBeCalledWith({ @@ -140,8 +133,7 @@ it('renders with cwe', () => { location={locationWithCWE} params={owaspParams} router={{ push: jest.fn() }} - />, - { context } + /> ); expect(getSecurityHotspots).toBeCalledWith({ project: 'foo', @@ -159,10 +151,7 @@ it('handle checkbox for cwe display', async () => { location={location} params={owaspParams} router={{ push: jest.fn() }} - />, - { - context - } + /> ); expect(getSecurityHotspots).toBeCalledWith({ project: 'foo', @@ -191,10 +180,7 @@ it('renders sansTop25', () => { location={location} params={sansParams} router={{ push: jest.fn() }} - />, - { - context - } + /> ); expect(getSecurityHotspots).toBeCalledWith({ project: 'foo', diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TokenStep-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TokenStep-test.tsx index 947b255b5f6..89266e206ec 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TokenStep-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TokenStep-test.tsx @@ -18,14 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { shallow } from 'enzyme'; import TokenStep from '../TokenStep'; -import { - change, - click, - submit, - waitAndUpdate, - shallowWithIntl -} from '../../../../helpers/testUtils'; +import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils'; jest.mock('../../../../api/user-tokens', () => ({ getTokens: () => Promise.resolve([{ name: 'foo' }]), @@ -38,7 +33,7 @@ jest.mock('../../../../components/icons-components/ClearIcon'); const currentUser = { login: 'user' }; it('generates token', async () => { - const wrapper = shallowWithIntl( + const wrapper = shallow( <TokenStep currentUser={currentUser} finished={false} @@ -58,7 +53,7 @@ it('generates token', async () => { }); it('revokes token', async () => { - const wrapper = shallowWithIntl( + const wrapper = shallow( <TokenStep currentUser={currentUser} finished={false} @@ -83,7 +78,7 @@ it('revokes token', async () => { it('continues', async () => { const onContinue = jest.fn(); - const wrapper = shallowWithIntl( + const wrapper = shallow( <TokenStep currentUser={currentUser} finished={false} @@ -101,7 +96,7 @@ it('continues', async () => { it('uses existing token', async () => { const onContinue = jest.fn(); - const wrapper = shallowWithIntl( + const wrapper = shallow( <TokenStep currentUser={currentUser} finished={false} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx index 1df6e78e993..1d30dfc7d8a 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx @@ -48,10 +48,6 @@ export class OnboardingModal extends React.PureComponent<Props> { } } - handleOpenProjectOnboarding = () => { - this.props.onOpenProjectOnboarding(); - }; - render() { if (!isLoggedIn(this.props.currentUser)) { return null; @@ -70,7 +66,7 @@ export class OnboardingModal extends React.PureComponent<Props> { <p className="spacer-top">{translate('onboarding.header.description')}</p> </div> <div className="modal-simple-body text-center onboarding-choices"> - <Button className="onboarding-choice" onClick={this.handleOpenProjectOnboarding}> + <Button className="onboarding-choice" onClick={this.props.onOpenProjectOnboarding}> <OnboardingProjectIcon className="big-spacer-bottom" /> <h6 className="onboarding-choice-name">{translate('onboarding.analyze_your_code')}</h6> </Button> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx index f8884dd68f1..2df79bd4a3f 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx @@ -18,12 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { InjectedRouter } from 'react-router'; import OnboardingModal from './OnboardingModal'; import { skipOnboarding } from '../../../store/users'; import TeamOnboardingModal from '../teamOnboarding/TeamOnboardingModal'; +import { OnboardingContext } from '../../../app/components/OnboardingContext'; interface DispatchProps { skipOnboarding: () => void; @@ -43,10 +43,6 @@ interface State { } export class OnboardingPage extends React.PureComponent<OwnProps & DispatchProps, State> { - static contextTypes = { - openProjectOnboarding: PropTypes.func.isRequired - }; - state: State = { modal: ModalKey.onboarding }; closeOnboarding = () => { @@ -63,11 +59,15 @@ export class OnboardingPage extends React.PureComponent<OwnProps & DispatchProps return ( <> {modal === ModalKey.onboarding && ( - <OnboardingModal - onClose={this.closeOnboarding} - onOpenProjectOnboarding={this.context.openProjectOnboarding} - onOpenTeamOnboarding={this.openTeamOnboarding} - /> + <OnboardingContext.Consumer> + {openProjectOnboarding => ( + <OnboardingModal + onClose={this.closeOnboarding} + onOpenProjectOnboarding={openProjectOnboarding} + onOpenTeamOnboarding={this.openTeamOnboarding} + /> + )} + </OnboardingContext.Consumer> )} {modal === ModalKey.teamOnboarding && ( <TeamOnboardingModal onFinish={this.closeOnboarding} /> diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx index 3ff570e8cde..24bd0ef5e63 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx @@ -39,15 +39,13 @@ it('should correctly open the different tutorials', () => { const onClose = jest.fn(); const onOpenProjectOnboarding = jest.fn(); const onOpenTeamOnboarding = jest.fn(); - const push = jest.fn(); const wrapper = shallow( <OnboardingModal currentUser={{ isLoggedIn: true }} onClose={onClose} onOpenProjectOnboarding={onOpenProjectOnboarding} onOpenTeamOnboarding={onOpenTeamOnboarding} - />, - { context: { router: { push } } } + /> ); click(wrapper.find('ResetButtonLink')); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap index 423382e72c1..23ded61ecb6 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap @@ -25,7 +25,7 @@ exports[`renders correctly 1`] = ` > <Button className="onboarding-choice" - onClick={[Function]} + onClick={[MockFunction]} > <OnboardingProjectIcon className="big-spacer-bottom" diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx index b706ad6a175..b973618c959 100644 --- a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx @@ -85,9 +85,6 @@ function getWrapper(props: Partial<UsersApp['props']> = {}) { organizationsEnabled={true} router={{ push: jest.fn() }} {...props} - />, - { - context: { router: {} } - } + /> ); } diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx index 34ce61ab17e..f3be5b6d6a0 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx @@ -40,6 +40,7 @@ import { import { isSameBranchLike, getBranchLikeQuery } from '../../helpers/branches'; import { translate } from '../../helpers/l10n'; import { Alert } from '../ui/Alert'; +import { WorkspaceContext } from '../workspace/context'; import './styles.css'; // TODO react-virtualized @@ -607,14 +608,19 @@ export default class SourceViewerBase extends React.PureComponent<Props, State> /* eslint-enable no-underscore-dangle */ return ( - <DuplicationPopup - blocks={blocks} - branchLike={this.props.branchLike} - duplicatedFiles={duplicatedFiles} - inRemovedComponent={inRemovedComponent} - onClose={this.closeLinePopup} - sourceViewerFile={component} - /> + <WorkspaceContext.Consumer> + {({ openComponent }) => ( + <DuplicationPopup + blocks={blocks} + branchLike={this.props.branchLike} + duplicatedFiles={duplicatedFiles} + inRemovedComponent={inRemovedComponent} + onClose={this.closeLinePopup} + openComponent={openComponent} + sourceViewerFile={component} + /> + )} + </WorkspaceContext.Consumer> ); }; @@ -698,12 +704,15 @@ export default class SourceViewerBase extends React.PureComponent<Props, State> return ( <div className={className} ref={node => (this.node = node)}> - {this.state.component && ( - <SourceViewerHeader - branchLike={this.props.branchLike} - sourceViewerFile={this.state.component} - /> - )} + <WorkspaceContext.Consumer> + {({ openComponent }) => ( + <SourceViewerHeader + branchLike={this.props.branchLike} + openComponent={openComponent} + sourceViewerFile={component} + /> + )} + </WorkspaceContext.Consumer> {sourceRemoved && ( <Alert className="spacer-top" variant="warning"> {translate('code_viewer.no_source_code_displayed_due_to_source_removed')} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx index 5bf963a9fb1..e63caa8ef21 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx @@ -20,7 +20,6 @@ import { stringify } from 'querystring'; import * as React from 'react'; import { Link } from 'react-router'; -import * as PropTypes from 'prop-types'; import MeasuresOverlay from './components/MeasuresOverlay'; import QualifierIcon from '../icons-components/QualifierIcon'; import Dropdown from '../controls/Dropdown'; @@ -28,7 +27,7 @@ import Favorite from '../controls/Favorite'; import ListIcon from '../icons-components/ListIcon'; import { ButtonIcon } from '../ui/buttons'; import { PopupPlacement } from '../ui/popups'; -import { WorkspaceContext } from '../workspace/context'; +import { WorkspaceContextShape } from '../workspace/context'; import { getPathUrlAsString, getBranchLikeUrl, @@ -43,6 +42,7 @@ import { omitNil } from '../../helpers/request'; interface Props { branchLike: T.BranchLike | undefined; + openComponent: WorkspaceContextShape['openComponent']; sourceViewerFile: T.SourceViewerFile; } @@ -51,12 +51,6 @@ interface State { } export default class SourceViewerHeader extends React.PureComponent<Props, State> { - context!: { workspace: WorkspaceContext }; - - static contextTypes = { - workspace: PropTypes.object.isRequired - }; - state: State = { measuresOverlay: false }; handleShowMeasuresClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { @@ -71,7 +65,7 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State openInWorkspace = (event: React.SyntheticEvent<HTMLAnchorElement>) => { event.preventDefault(); const { key } = this.props.sourceViewerFile; - this.context.workspace.openComponent({ branchLike: this.props.branchLike, key }); + this.props.openComponent({ branchLike: this.props.branchLike, key }); }; render() { diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx index 1f348fde012..da22591c654 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx @@ -19,12 +19,11 @@ */ import * as React from 'react'; import { Link } from 'react-router'; -import * as PropTypes from 'prop-types'; import { groupBy, sortBy } from 'lodash'; import { DropdownOverlay } from '../../controls/Dropdown'; import QualifierIcon from '../../icons-components/QualifierIcon'; import { PopupPlacement } from '../../ui/popups'; -import { WorkspaceContext } from '../../workspace/context'; +import { WorkspaceContextShape } from '../../workspace/context'; import { isShortLivingBranch, isPullRequest } from '../../../helpers/branches'; import { translate } from '../../../helpers/l10n'; import { collapsedDirFromPath, fileFromPath } from '../../../helpers/path'; @@ -37,17 +36,11 @@ interface Props { duplicatedFiles?: { [ref: string]: T.DuplicatedFile }; inRemovedComponent: boolean; onClose: () => void; - popupPosition?: any; + openComponent: WorkspaceContextShape['openComponent']; sourceViewerFile: T.SourceViewerFile; } export default class DuplicationPopup extends React.PureComponent<Props> { - context!: { workspace: WorkspaceContext }; - - static contextTypes = { - workspace: PropTypes.object.isRequired - }; - shouldLink() { const { branchLike } = this.props; return !isShortLivingBranch(branchLike) && !isPullRequest(branchLike); @@ -65,7 +58,7 @@ export default class DuplicationPopup extends React.PureComponent<Props> { event.currentTarget.blur(); const { key, line } = event.currentTarget.dataset; if (this.shouldLink() && key) { - this.context.workspace.openComponent({ + this.props.openComponent({ branchLike: this.props.branchLike, key, line: line ? Number(line) : undefined diff --git a/server/sonar-web/src/main/js/components/controls/DateInput.tsx b/server/sonar-web/src/main/js/components/controls/DateInput.tsx index 4da2144f9ac..d1d57fffda8 100644 --- a/server/sonar-web/src/main/js/components/controls/DateInput.tsx +++ b/server/sonar-web/src/main/js/components/controls/DateInput.tsx @@ -20,7 +20,7 @@ import * as React from 'react'; import * as classNames from 'classnames'; import { DayModifiers, Modifier, Modifiers } from 'react-day-picker'; -import { intlShape, InjectedIntlProps } from 'react-intl'; +import { InjectedIntlProps, injectIntl } from 'react-intl'; import { range } from 'lodash'; import * as addMonths from 'date-fns/add_months'; import * as setMonth from 'date-fns/set_month'; @@ -41,7 +41,7 @@ import './styles.css'; const DayPicker = lazyLoad(() => import('react-day-picker')); -export interface Props { +interface Props { className?: string; currentMonth?: Date; highlightFrom?: Date; @@ -64,13 +64,8 @@ interface State { type Week = [string, string, string, string, string, string, string]; export default class DateInput extends React.PureComponent<Props, State> { - context!: InjectedIntlProps; input?: HTMLInputElement | null; - static contextTypes = { - intl: intlShape - }; - constructor(props: Props) { super(props); this.state = { currentMonth: props.value || props.currentMonth || new Date(), open: false }; @@ -129,10 +124,7 @@ export default class DateInput extends React.PureComponent<Props, State> { render() { const { highlightFrom, highlightTo, minDate, value } = this.props; - const { formatDate } = this.context.intl; const { lastHovered } = this.state; - const formattedValue = - value && formatDate(value, { year: 'numeric', month: 'short', day: 'numeric' }); const after = this.props.maxDate || new Date(); @@ -158,17 +150,17 @@ export default class DateInput extends React.PureComponent<Props, State> { return ( <OutsideClickHandler onClickOutside={this.closeCalendar}> <span className={classNames('date-input-control', this.props.className)}> - <input + <InputWrapper className={classNames('date-input-control-input', this.props.inputClassName, { 'is-filled': this.props.value !== undefined })} + innerRef={node => (this.input = node)} name={this.props.name} onFocus={this.openCalendar} placeholder={this.props.placeholder} readOnly={true} - ref={node => (this.input = node)} type="text" - value={formattedValue || ''} + value={value} /> <CalendarIcon className="date-input-control-icon" fill="" /> {this.props.value !== undefined && ( @@ -230,3 +222,15 @@ export default class DateInput extends React.PureComponent<Props, State> { function NullComponent() { return null; } + +type InputWrapperProps = T.Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value'> & + InjectedIntlProps & { + innerRef: React.Ref<HTMLInputElement>; + value: Date | undefined; + }; + +const InputWrapper = injectIntl(({ innerRef, intl, value, ...other }: InputWrapperProps) => { + const formattedValue = + value && intl.formatDate(value, { year: 'numeric', month: 'short', day: 'numeric' }); + return <input {...other} ref={innerRef} value={formattedValue || ''} />; +}); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/DateInput-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/DateInput-test.tsx index 4a009334233..9cb742d98ad 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/DateInput-test.tsx +++ b/server/sonar-web/src/main/js/components/controls/__tests__/DateInput-test.tsx @@ -18,13 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { shallow } from 'enzyme'; import * as addDays from 'date-fns/add_days'; import * as setMonth from 'date-fns/set_month'; import * as setYear from 'date-fns/set_year'; import * as subDays from 'date-fns/sub_days'; import * as subMonths from 'date-fns/sub_months'; -import DateInput, { Props } from '../DateInput'; -import { shallowWithIntl } from '../../../helpers/testUtils'; +import DateInput from '../DateInput'; import { parseDate } from '../../../helpers/dates'; jest.mock('../../lazyLoad', () => ({ @@ -111,8 +111,8 @@ it('should hightlightTo range', () => { expect(dayPicker.prop('selectedDays')).toEqual([dateB]); }); -function shallowRender(props?: Partial<Props>) { - const wrapper = shallowWithIntl<DateInput>( +function shallowRender(props?: Partial<DateInput['props']>) { + const wrapper = shallow<DateInput>( <DateInput currentMonth={dateA} maxDate={dateB} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/DateInput-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/DateInput-test.tsx.snap index 5485b05984e..f4dc4048d63 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/DateInput-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/DateInput-test.tsx.snap @@ -7,13 +7,13 @@ exports[`should render 1`] = ` <span className="date-input-control" > - <input + <InjectIntl(Component) className="date-input-control-input" + innerRef={[Function]} onFocus={[Function]} placeholder="placeholder" readOnly={true} type="text" - value="" /> <CalendarIcon className="date-input-control-icon" @@ -30,13 +30,14 @@ exports[`should render 2`] = ` <span className="date-input-control" > - <input + <InjectIntl(Component) className="date-input-control-input is-filled" + innerRef={[Function]} onFocus={[Function]} placeholder="placeholder" readOnly={true} type="text" - value="Jan 17, 2018" + value={2018-01-17T00:00:00.000Z} /> <CalendarIcon className="date-input-control-icon" @@ -62,13 +63,14 @@ exports[`should render 3`] = ` <span className="date-input-control" > - <input + <InjectIntl(Component) className="date-input-control-input is-filled" + innerRef={[Function]} onFocus={[Function]} placeholder="placeholder" readOnly={true} type="text" - value="Jan 17, 2018" + value={2018-01-17T00:00:00.000Z} /> <CalendarIcon className="date-input-control-icon" @@ -269,13 +271,13 @@ exports[`should select a day 1`] = ` <span className="date-input-control" > - <input + <InjectIntl(Component) className="date-input-control-input" + innerRef={[Function]} onFocus={[Function]} placeholder="placeholder" readOnly={true} type="text" - value="" /> <CalendarIcon className="date-input-control-icon" diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx index 226a6547d5f..8a6e4934ca5 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx @@ -22,25 +22,20 @@ import EllipsisIcon from '../../icons-components/EllipsisIcon'; import Tooltip from '../../controls/Tooltip'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { Button } from '../../ui/buttons'; -import { WorkspaceContext } from '../../workspace/context'; +import { WorkspaceContextShape } from '../../workspace/context'; interface Props { engine?: string; manualVulnerability: boolean; message: string; + openRule: WorkspaceContextShape['openRule']; organization: string; rule: string; } export default class IssueMessage extends React.PureComponent<Props> { - context!: { workspace: WorkspaceContext }; - - static contextTypes = { - workspace: () => null - }; - handleClick = () => { - this.context.workspace.openRule({ + this.props.openRule({ key: this.props.rule, organization: this.props.organization }); diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx index 5f0f7d585cd..c6989ce39e4 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx @@ -29,6 +29,7 @@ import { getBranchLikeQuery } from '../../../helpers/branches'; import { getComponentIssuesUrl } from '../../../helpers/urls'; import { formatMeasure } from '../../../helpers/measures'; import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { WorkspaceContext } from '../../workspace/context'; interface Props { branchLike?: T.BranchLike; @@ -69,13 +70,18 @@ export default function IssueTitleBar(props: Props) { return ( <div className="issue-row"> - <IssueMessage - engine={issue.externalRuleEngine} - manualVulnerability={issue.fromHotspot && issue.type === 'VULNERABILITY'} - message={issue.message} - organization={issue.organization} - rule={issue.rule} - /> + <WorkspaceContext.Consumer> + {({ openRule }) => ( + <IssueMessage + engine={issue.externalRuleEngine} + manualVulnerability={issue.fromHotspot && issue.type === 'VULNERABILITY'} + message={issue.message} + openRule={openRule} + organization={issue.organization} + rule={issue.rule} + /> + )} + </WorkspaceContext.Consumer> <div className="issue-row-meta"> <ul className="issue-meta-list"> diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx index 55b5241bacb..25c7a39daeb 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx @@ -26,6 +26,7 @@ it('should render with the message and a link to open the rule', () => { <IssueMessage manualVulnerability={false} message="Reduce the number of conditional operators (4) used in the expression" + openRule={jest.fn()} organization="myorg" rule="javascript:S1067" /> diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap index 09c82801eea..3fc0b16bfe3 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap @@ -10,12 +10,9 @@ exports[`should render the titlebar correctly 1`] = ` <div className="issue-row" > - <IssueMessage - manualVulnerability={false} - message="Reduce the number of conditional operators (4) used in the expression" - organization="myorg" - rule="javascript:S1067" - /> + <ContextConsumer> + <Component /> + </ContextConsumer> <div className="issue-row-meta" > @@ -109,12 +106,9 @@ exports[`should render the titlebar with the filter 1`] = ` <div className="issue-row" > - <IssueMessage - manualVulnerability={false} - message="Reduce the number of conditional operators (4) used in the expression" - organization="myorg" - rule="javascript:S1067" - /> + <ContextConsumer> + <Component /> + </ContextConsumer> <div className="issue-row-meta" > diff --git a/server/sonar-web/src/main/js/components/workspace/Workspace.tsx b/server/sonar-web/src/main/js/components/workspace/Workspace.tsx index 3f35a5fdf9b..00896f5e86c 100644 --- a/server/sonar-web/src/main/js/components/workspace/Workspace.tsx +++ b/server/sonar-web/src/main/js/components/workspace/Workspace.tsx @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import { omit, uniqBy } from 'lodash'; import { WorkspaceContext, ComponentDescriptor, RuleDescriptor } from './context'; import WorkspacePortal from './WorkspacePortal'; @@ -48,19 +47,11 @@ const TYPE_KEY = '__type__'; export default class Workspace extends React.PureComponent<{}, State> { mounted = false; - static childContextTypes = { - workspace: PropTypes.object - }; - constructor(props: {}) { super(props); this.state = { height: INITIAL_HEIGHT, open: {}, ...this.loadWorkspace() }; } - getChildContext = (): { workspace: WorkspaceContext } => { - return { workspace: { openComponent: this.openComponent, openRule: this.openRule } }; - }; - componentDidMount() { this.mounted = true; } @@ -187,7 +178,8 @@ export default class Workspace extends React.PureComponent<{}, State> { const height = this.state.maximized ? window.innerHeight * MAX_HEIGHT : this.state.height; return ( - <> + <WorkspaceContext.Provider + value={{ openComponent: this.openComponent, openRule: this.openRule }}> {this.props.children} <WorkspacePortal> {(components.length > 0 || rules.length > 0) && ( @@ -228,7 +220,7 @@ export default class Workspace extends React.PureComponent<{}, State> { /> )} </WorkspacePortal> - </> + </WorkspaceContext.Provider> ); } } diff --git a/server/sonar-web/src/main/js/components/workspace/context.ts b/server/sonar-web/src/main/js/components/workspace/context.ts index 0193e811238..4de9a9b6c7c 100644 --- a/server/sonar-web/src/main/js/components/workspace/context.ts +++ b/server/sonar-web/src/main/js/components/workspace/context.ts @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { createContext } from 'react'; export interface ComponentDescriptor { branchLike: T.BranchLike | undefined; @@ -32,7 +33,12 @@ export interface RuleDescriptor { organization: string; } -export interface WorkspaceContext { +export interface WorkspaceContextShape { openComponent: (component: ComponentDescriptor) => void; openRule: (rule: RuleDescriptor) => void; } + +export const WorkspaceContext = createContext<WorkspaceContextShape>({ + openComponent: () => {}, + openRule: () => {} +}); diff --git a/server/sonar-web/src/main/js/helpers/testUtils.ts b/server/sonar-web/src/main/js/helpers/testUtils.ts index ed1f5f42a4b..399363f6418 100644 --- a/server/sonar-web/src/main/js/helpers/testUtils.ts +++ b/server/sonar-web/src/main/js/helpers/testUtils.ts @@ -17,9 +17,8 @@ * 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, ShallowRendererProps, ShallowWrapper, ReactWrapper } from 'enzyme'; +import { ShallowWrapper, ReactWrapper } from 'enzyme'; import { InjectedRouter } from 'react-router'; -import { IntlProvider } from 'react-intl'; export const mockEvent = { target: { blur() {} }, @@ -112,16 +111,6 @@ export function doAsync(fn?: Function): Promise<void> { }); } -// Create the IntlProvider to retrieve context for wrapping around. -const intlProvider = new IntlProvider({ locale: 'en' }, {}); -const { intl } = intlProvider.getChildContext(); -export function shallowWithIntl<C extends React.Component>( - node: React.ReactElement<any>, - options: ShallowRendererProps = {} -) { - return shallow<C>(node, { ...options, context: { intl, ...options.context } }); -} - export async function waitAndUpdate(wrapper: ShallowWrapper<any, any> | ReactWrapper<any, any>) { await new Promise(setImmediate); wrapper.update(); |