]> source.dussan.org Git - sonarqube.git/commitdiff
finish removing legacy react context (#1055)
authorStas Vilchik <stas.vilchik@sonarsource.com>
Wed, 12 Dec 2018 09:39:12 +0000 (10:39 +0100)
committerSonarTech <sonartech@sonarsource.com>
Wed, 12 Dec 2018 19:21:03 +0000 (20:21 +0100)
50 files changed:
server/sonar-web/src/main/js/app/components/App.tsx
server/sonar-web/src/main/js/app/components/OnboardingContext.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/StartupModal.tsx
server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx
server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx
server/sonar-web/src/main/js/apps/code/components/Component.tsx
server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/AvailableSinceFacet.tsx
server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.tsx
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap
server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap
server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.tsx
server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx
server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationJustCreated-test.tsx
server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx
server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityDateInput-test.tsx
server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx
server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx
server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilterContainer-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.tsx
server/sonar-web/src/main/js/apps/securityReports/components/__tests__/App-test.tsx
server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TokenStep-test.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap
server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-test.tsx
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx
server/sonar-web/src/main/js/components/controls/DateInput.tsx
server/sonar-web/src/main/js/components/controls/__tests__/DateInput-test.tsx
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/DateInput-test.tsx.snap
server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx
server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap
server/sonar-web/src/main/js/components/workspace/Workspace.tsx
server/sonar-web/src/main/js/components/workspace/context.ts
server/sonar-web/src/main/js/helpers/testUtils.ts

index d3f99a4985bbd1239433c3c5393c852305714d68..c4c50beb7eea9cf70c1b7fbfb3316a2013849460 100644 (file)
@@ -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 (file)
index 0000000..9b8d000
--- /dev/null
@@ -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>(() => {});
index c26d26f032a7019552086850764205833a08f59d..8d70aa9d802b09c36fb29ba889f89602672a03b1 100644 (file)
@@ -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>
     );
   }
 }
index a136ce1951834fd4ee443c6aeece5e47e1493dc7..ef1d79fe8ce1fa8c6f5cac687bf9843cb44881b7 100644 (file)
@@ -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>
index e090986643f2c96f5f42217daab3c89b5492a156..336d8734b389398ecf9579a3752cab43f0648601 100644 (file)
@@ -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 {
index 1656f723eda659fcba7901df0b433c2a6829c80d..edcf79720848db4fefe3b36f27f264682f4648f1 100644 (file)
@@ -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);
 }
index 9352241bd264aad0c5e9d56d908b2733d2dd2822..3e3dc6e6e3c29b7cb5e1f383e3184dd3af56cbe9 100644 (file)
@@ -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} />;
index f486ce21bf394d3cccc072fc8ba416054ee508ed..424c0d0f7b0311be3518a249e0e1931e6f4722ca 100644 (file)
  * 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,
index 7bc746a447bbbf802a37dac2dd36451714e6c1a7..ebe5996c7e2facca88706f0c3ad40a942342f3df 100644 (file)
@@ -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);
index 9f145e734afb9c2c9dc1d3507804a07cdac744e6..41c3829f368380b8c0e5bb6b631064d4c3356df5 100644 (file)
@@ -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);
index 3aa1046f6dc2bb04efb6cee4acd275c449323ae0..bbb14e014d87347d75713e90977a805aca6b4420 100644 (file)
@@ -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}
+    />
+  );
 }
index 7a4677c1b7c4c5d44cde549405a0e40e9d6cdb75..7de2ed12e3b72daf07ede7afebc79493d3205af7 100644 (file)
@@ -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 {
index ff574566f0dbfeeefba75d840ba4bc5b5448cf2b..85980a6ec2bcde631eb8fad8ec7d45c07c7b603b 100644 (file)
  * 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();
index 240d69eaf9879843936a28790183d7d64fc713c0..5a92120c88d2d38c3d1d7aeb5f7855e9baa305ba 100644 (file)
@@ -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);
index bc2e66a808c1c60bce2ba9c3e1b25962d8d2b503..d12f303876010b6629acf0761c2d110c8dec7fae 100644 (file)
@@ -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",
index 23751fc6dc42e8762727bd03825ac7c59df76b2f..89f1d217de574891b983ac0bef4f433026b823ec 100644 (file)
  * 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 = () => {
index f0d281877eb69687d356f409c3047694b82d180e..d8770331308d70ad3bf85f27bf2a18c1ac655119 100644 (file)
@@ -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
     );
index 08ee42d00db755cb02b56720125ce77652cffa8c..0bcc3631ed521ca87a8fc315a2707fb27ee2d9bc 100644 (file)
@@ -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');
 });
index fd3615653aa7156c2fd5d820bee8987aa533106d..28e94e67a51b426321352771754fbdd248e44d1e 100644 (file)
@@ -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);
index f4a39fd5f13ebc7a585a007a6f5bd2064c867087..48efcd8ce5d48a101ebdb74cac559a705a103236 100644 (file)
@@ -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}
+    />
+  );
 }
index fa65ca538d0a4e30ba8a373369232e376c475111..c2dacd4fe90f540c302d4996aed66d8e0ddbb38c 100644 (file)
  * 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={() => {}}
index 790e0970db355a7ae0cebaf699ba56771b0f5bb2..4ef97f6d99fb54757f85e63751ccf51cf5af2c31 100644 (file)
  * 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() {
index c28dc6312b7e62d1f1838eed22aef2aeac952972..f192a900b14a98ac444d3f550798172547d7992d 100644 (file)
@@ -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 => ({
index 8bb2f535070b11a51bf0b7034d9f5c37eb63c4da..f54e32f95d28633c43125dd8d250a9239b046baa 100644 (file)
@@ -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>
     );
   }
 
index 6cc4deb62f015282244f6eeb9162cb9ffa13815b..667a36abe8a3437683b28e529ec9f58650c3b4dd 100644 (file)
@@ -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' }}
       />
     )
index 0e0f2af3a931d1f26ca6bcabcc6c9d58d849845b..35804e7f3824febd6609e7ec60fd2295027d8ce3 100644 (file)
@@ -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();
 });
index 5b57d76dec36446d39805c844a76877280a267aa..d1b0a82b9c60e990e1f0f8b9470cd6d3425ee02d 100644 (file)
@@ -46,7 +46,7 @@ exports[`renders for SonarCloud 1`] = `
       className="huge-spacer-top"
     >
       <Button
-        onClick={[Function]}
+        onClick={[MockFunction]}
       >
         provisioning.analyze_new_project
       </Button>
index bc326ba647758ae641fd67817efd6896ec61f51f..14bf2862347eb032b0ae71bfe37f2378a6efe24a 100644 (file)
@@ -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>
 `;
index ca70676d485aed5a247548dd36231a268417297d..3af18a5a408f915afc090043c6cbb8c04fa028f2 100644 (file)
@@ -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' });
index 4bc227b99493b28a5f062a66d06ed7f8c19748f8..25f2ecddd41b37886dbb7f707ff1d54075a5a0c6 100644 (file)
@@ -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' });
index 983786ae8dfe141103ed4fd7a501424035f5ea76..162ce640974c274913f002df4d69752dab3d90ce 100644 (file)
@@ -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',
index 947b255b5f68c3648068262908e60506eb885951..89266e206ec32382c5072f2a6e88cae3205f42cc 100644 (file)
  * 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}
index 1df6e78e993203eedef30faa61887b7ac006c55b..1d30dfc7d8ac31e3c712d5eac2e674c202ddd0e3 100644 (file)
@@ -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>
index f8884dd68f1c21d2056a89014b36deb13ceff721..2df79bd4a3fcb2bca56bf4f6579f8b1c2b747347 100644 (file)
  * 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} />
index 3ff570e8cdef16a08828163c417d2a34a54c22cd..24bd0ef5e63a093fc7802c6f8975da81e73d7c60 100644 (file)
@@ -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'));
index 423382e72c1cf630100cd66f237e8bd30768ed8f..23ded61ecb687da907f7647e49e23e3c42c527cd 100644 (file)
@@ -25,7 +25,7 @@ exports[`renders correctly 1`] = `
   >
     <Button
       className="onboarding-choice"
-      onClick={[Function]}
+      onClick={[MockFunction]}
     >
       <OnboardingProjectIcon
         className="big-spacer-bottom"
index b706ad6a1750d3e85669d199dc3a1d1dafa6149d..b973618c95960d0630cf336a899e2adc2edf5f20 100644 (file)
@@ -85,9 +85,6 @@ function getWrapper(props: Partial<UsersApp['props']> = {}) {
       organizationsEnabled={true}
       router={{ push: jest.fn() }}
       {...props}
-    />,
-    {
-      context: { router: {} }
-    }
+    />
   );
 }
index 34ce61ab17ec5689ecbb52c50efeabe8e3d33bc8..f3be5b6d6a082906fdf1a4b6aa701472f9f344ac 100644 (file)
@@ -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')}
index 5bf963a9fb1ec019158625f9379dfda277420143..e63caa8ef2148bd02efacfd61e3af95f8086694c 100644 (file)
@@ -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() {
index 1f348fde012c02565b7fcaf560c641088e779769..da22591c6544341f4960e05bb616b3bf1c4511ca 100644 (file)
  */
 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
index 4da2144f9ac0af05186dfa26cbd72e2e9e23a7a9..d1d57fffda88f2da018d61582f61c03fd9898992 100644 (file)
@@ -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 || ''} />;
+});
index 4a0093342338d30def80631bab9d9e172ab0792e..9cb742d98ad0816bb29746833a38070ec6539ac5 100644 (file)
  * 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}
index 5485b05984e4e6a4c52e967f0b7a2c9dd1fb61e9..f4dc4048d6305070f5c5767de78d2900aa1432d2 100644 (file)
@@ -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"
index 226a6547d5f7984e620e045775a5d0833edb329f..8a6e4934ca51979397abed3046fa8a9b863494a2 100644 (file)
@@ -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
     });
index 5f0f7d585cd8e076ce20b0697ade326f17b1d6dc..c6989ce39e488b04015e7be99ea271ef2d9b52ff 100644 (file)
@@ -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">
index 55b5241bacbba142a24588cbf1e8047465e0090a..25c7a39daebd0163546151b48a88eb73ca9a7700 100644 (file)
@@ -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"
     />
index 09c82801eea11828ea169d28823e03b4ea997384..3fc0b16bfe37e4e63287b741e5ee4745e83d7258 100644 (file)
@@ -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"
   >
index 3f35a5fdf9b487f55c34784058b8b3542eaae283..00896f5e86c8e341788d4f1982b06b3974c9d9b3 100644 (file)
@@ -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>
     );
   }
 }
index 0193e8112385aca8e5832a4ec56029cc6df6ebd5..4de9a9b6c7c5f8fd4b8a36d08367beb6a68efea6 100644 (file)
@@ -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: () => {}
+});
index ed1f5f42a4b48b96ccc59c89b0481955a0c7e8f1..399363f64189679985ab37cc26e3f318aad3aa68 100644 (file)
@@ -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();