From 5d315a6b9efcc41ca0bf9af2c1585fefffcb53fe Mon Sep 17 00:00:00 2001 From: Antonin Cezard Date: Fri, 3 Feb 2023 15:36:41 +0100 Subject: [PATCH] feat: implement router v6 Also fix tests that broke after the upgrade --- src/components/Home.jsx | 14 +++--- src/components/IntentRedirect.jsx | 6 +-- src/components/Konnector.jsx | 38 ++++++++-------- src/components/Konnector.spec.jsx | 60 ++++++++++++++++++------- src/components/KonnectorErrors.jsx | 55 +++++++++++------------ src/components/KonnectorErrors.spec.jsx | 8 ++-- src/components/KonnectorTile.jsx | 28 ++++++------ src/containers/AccountConnection.jsx | 21 +++++---- src/containers/App.jsx | 59 ++++++++++++------------ src/containers/App.spec.jsx | 39 +++++++++------- 10 files changed, 181 insertions(+), 147 deletions(-) diff --git a/src/components/Home.jsx b/src/components/Home.jsx index bfeb9e55f7..e5e8f02ea7 100644 --- a/src/components/Home.jsx +++ b/src/components/Home.jsx @@ -1,20 +1,19 @@ import React, { Component } from 'react' -import { Route, withRouter } from 'react-router' +import { Outlet } from 'react-router-dom' -import { translate } from 'cozy-ui/transpiled/react/I18n' +import { CozyConfirmDialogProvider } from 'cozy-harvest-lib' import { Main, Content } from 'cozy-ui/transpiled/react/Layout' -import Konnector from 'components/Konnector' import Applications from 'components/Applications' +import FooterLogo from 'components/FooterLogo' import ScrollToTopOnMount from 'components/ScrollToTopOnMount' import Services from 'components/Services' -import FooterLogo from 'components/FooterLogo' import Shortcuts from 'components/Shortcuts' -import { CozyConfirmDialogProvider } from 'cozy-harvest-lib' class Home extends Component { render() { const { setAppsReady, wrapper } = this.props + return (
@@ -25,11 +24,12 @@ class Home extends Component { -
+ +
) } } -export default withRouter(translate()(Home)) +export default Home diff --git a/src/components/IntentRedirect.jsx b/src/components/IntentRedirect.jsx index 284603d49a..73bc422a60 100644 --- a/src/components/IntentRedirect.jsx +++ b/src/components/IntentRedirect.jsx @@ -1,12 +1,12 @@ /* global cozy */ import React from 'react' -import { Redirect } from 'react-router-dom' +import { Redirect, useSearchParams } from 'react-router-dom' import { connect } from 'react-redux' import { getInstalledKonnectors } from 'reducers' -const IntentRedirect = ({ installedKonnectors, location }) => { - const queryString = !!location && location.search +const IntentRedirect = ({ installedKonnectors }) => { + const queryString = useSearchParams() const query = queryString && queryString diff --git a/src/components/Konnector.jsx b/src/components/Konnector.jsx index 2a03e50575..bb011554d2 100644 --- a/src/components/Konnector.jsx +++ b/src/components/Konnector.jsx @@ -1,24 +1,23 @@ import React, { useCallback, useEffect } from 'react' import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom' -import flow from 'lodash/flow' +import { useLocation, useNavigate, useParams } from 'react-router-dom' -import { Routes as HarvestRoutes } from 'cozy-harvest-lib' import datacardOptions from 'cozy-harvest-lib/dist/datacards/datacardOptions' import log from 'cozy-logger' +import { Routes as HarvestRoutes } from 'cozy-harvest-lib' +import { closeApp, openApp } from 'hooks/useOpenApp' import { getKonnector } from 'ducks/konnectors' - import { getTriggersByKonnector } from 'reducers' -import { closeApp, openApp } from 'hooks/useOpenApp' -export const Konnector = ({ konnector, history, match, triggers }) => { - const { konnectorSlug } = match ? match.params : {} +export const StatelessKonnector = ({ konnector, triggers, slug }) => { + const navigate = useNavigate() const konnectorWithTriggers = konnector ? { ...konnector, triggers: { data: triggers } } : undefined - const onDismiss = useCallback(() => history.push('/connected'), [history]) - const slug = konnector?.slug || location.hash.split('/')[2] + const onDismiss = useCallback(() => navigate('/connected'), [navigate]) + const konnectorSlug = slug || konnector?.slug || location.hash.split('/')[2] + const location = useLocation() useEffect(() => { openApp() @@ -39,21 +38,24 @@ export const Konnector = ({ konnector, history, match, triggers }) => { return ( ) } -const mapStateToProps = (state, ownProps) => { - const { konnectorSlug } = ownProps.match.params - return { - konnector: getKonnector(state.oldcozy, konnectorSlug), - triggers: getTriggersByKonnector(state, konnectorSlug) - } +const StatefulKonnector = connect((state, { slug }) => ({ + konnector: getKonnector(state.oldcozy, slug), + triggers: getTriggersByKonnector(state, slug) +}))(StatelessKonnector) + +export const Konnector = () => { + const { konnectorSlug } = useParams() + + return } -export default flow(connect(mapStateToProps), withRouter)(Konnector) +export default Konnector diff --git a/src/components/Konnector.spec.jsx b/src/components/Konnector.spec.jsx index af5454dce5..1bafc7d575 100644 --- a/src/components/Konnector.spec.jsx +++ b/src/components/Konnector.spec.jsx @@ -1,9 +1,12 @@ +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() + import React from 'react' -import { Router } from 'react-router-dom' -import { createMemoryHistory } from 'history' -import { fireEvent, render } from '@testing-library/react' +import { fireEvent, render, screen } from '@testing-library/react' +import { Navigate, createMemoryRouter, RouterProvider } from 'react-router-dom' -import { Konnector } from './Konnector' +import { StatelessKonnector } from './Konnector' +import { act } from 'react-dom/test-utils' jest.mock('cozy-harvest-lib', () => ({ Routes: ({ konnector, triggers, onDismiss }) => ( @@ -13,22 +16,47 @@ jest.mock('cozy-harvest-lib', () => ({ ) })) -it('it correctly goes back to the home page onDismiss and allows nav goBack', () => { - const history = createMemoryHistory() - - const { getByText } = render( - - - +const setupMyTest = () => { + const router = createMemoryRouter( + [ + { + path: '*', + element: + }, + { + path: '/connected', + element:
Home
+ }, + { + path: '/connected/:konnectorSlug/*', + element: + } + ], + { + initialEntries: ['/connected'], + initialIndex: 0 + } ) - history.push('connected/alan/accounts/123') + render() + + return { router } +} + +it('it correctly goes back to the home page onDismiss and allows nav goBack', () => { + const { router } = setupMyTest() + + act(() => { + router.navigate('/connected/alan/accounts/123') + }) - fireEvent.click(getByText('alan')) + fireEvent.click(screen.getByText('alan')) - expect(history.location.pathname).toBe('/connected') + expect(router.state.location.pathname).toBe('/connected') - history.go(-1) + act(() => { + router.navigate(-1) + }) - expect(history.location.pathname).toBe('/connected/alan/accounts/123') + expect(router.state.location.pathname).toBe('/connected/alan/accounts/123') }) diff --git a/src/components/KonnectorErrors.jsx b/src/components/KonnectorErrors.jsx index 57dbfeda43..3f4563a332 100644 --- a/src/components/KonnectorErrors.jsx +++ b/src/components/KonnectorErrors.jsx @@ -1,31 +1,33 @@ -import React from 'react' import PropTypes from 'prop-types' -import { useClient, models } from 'cozy-client' -import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom' -import keyBy from 'lodash/keyBy' +import React from 'react' import flow from 'lodash/flow' -import Infos from 'cozy-ui/transpiled/react/Infos' -import Typography from 'cozy-ui/transpiled/react/Typography' -import InfosCarrousel from 'cozy-ui/transpiled/react/InfosCarrousel' +import keyBy from 'lodash/keyBy' +import { connect } from 'react-redux' +import { useClient, models } from 'cozy-client' +import { useNavigate } from 'react-router-dom' + +import AppIcon from 'cozy-ui/transpiled/react/AppIcon' import Button from 'cozy-ui/transpiled/react/Button' +import CrossButton from 'cozy-ui/transpiled/react/Icons/Cross' +import Divider from 'cozy-ui/transpiled/react/MuiCozyTheme/Divider' import Icon from 'cozy-ui/transpiled/react/Icon' import IconButton from 'cozy-ui/transpiled/react/IconButton' -import CrossButton from 'cozy-ui/transpiled/react/Icons/Cross' +import Infos from 'cozy-ui/transpiled/react/Infos' +import InfosCarrousel from 'cozy-ui/transpiled/react/InfosCarrousel' +import MuiCozyTheme from 'cozy-ui/transpiled/react/MuiCozyTheme' +import Typography from 'cozy-ui/transpiled/react/Typography' +import useBreakpoints from 'cozy-ui/transpiled/react/hooks/useBreakpoints' import { Media, Bd } from 'cozy-ui/transpiled/react/Media' +import { getErrorLocaleBound, KonnectorJobError } from 'cozy-harvest-lib' import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import useBreakpoints from 'cozy-ui/transpiled/react/hooks/useBreakpoints' -import Divider from 'cozy-ui/transpiled/react/MuiCozyTheme/Divider' -import MuiCozyTheme from 'cozy-ui/transpiled/react/MuiCozyTheme' + import { - getTriggersInError, getAccountsWithErrors, - getInstalledKonnectors + getInstalledKonnectors, + getTriggersInError } from 'reducers/index' import ReactMarkdownWrapper from 'components/ReactMarkdownWrapper' -import AppIcon from 'cozy-ui/transpiled/react/AppIcon' import homeConfig from 'config/home.json' -import { getErrorLocaleBound, KonnectorJobError } from 'cozy-harvest-lib' const { triggers: { triggers: triggersModel, triggerStates: triggerStatesModel }, @@ -70,8 +72,7 @@ const KonnectorError = ({ triggerErrors, index, konnectorsBySlug, - accountsById, - history + accountsById }) => { const client = useClient() const { t, lang } = useI18n() @@ -81,6 +82,7 @@ const KonnectorError = ({ const konnectorSlug = triggersModel.getKonnector(trigger) const konnectorAccount = triggersModel.getAccountId(trigger) const konnector = konnectorsBySlug[konnectorSlug] + const navigate = useNavigate() const errorTitle = getErrorLocaleBound(konnError, konnector, lang, 'title') @@ -139,9 +141,7 @@ const KonnectorError = ({ className="u-mh-0" size={isMobile ? 'small' : 'normal'} onClick={() => - history.push( - `/connected/${konnectorSlug}/accounts/${konnectorAccount}` - ) + navigate(`/connected/${konnectorSlug}/accounts/${konnectorAccount}`) } /> } @@ -152,8 +152,7 @@ const KonnectorError = ({ export const KonnectorErrors = ({ triggersInError, accountsWithErrors, - installedKonnectors, - history + installedKonnectors }) => { const accountsWithErrorsById = keyBy(accountsWithErrors, '_id') const installedKonnectorsBySlug = keyBy(installedKonnectors, getKonnectorSlug) @@ -180,7 +179,6 @@ export const KonnectorErrors = ({ {nonMutedTriggerErrors.map((trigger, index) => ( { return { - triggersInError: getTriggersInError(state), accountsWithErrors: getAccountsWithErrors(state), - installedKonnectors: getInstalledKonnectors(state) + installedKonnectors: getInstalledKonnectors(state), + triggersInError: getTriggersInError(state) } } -export default flow(connect(mapStateToProps), withRouter)(KonnectorErrors) +export default flow(connect(mapStateToProps))(KonnectorErrors) diff --git a/src/components/KonnectorErrors.spec.jsx b/src/components/KonnectorErrors.spec.jsx index bc55df63e5..37da816a79 100644 --- a/src/components/KonnectorErrors.spec.jsx +++ b/src/components/KonnectorErrors.spec.jsx @@ -6,6 +6,11 @@ import { render, fireEvent } from '@testing-library/react' jest.mock('cozy-ui/transpiled/react/AppIcon', () => () => null) +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => jest.fn() +})) + describe('KonnectorErrors', () => { const MOCKED_DATE = '2020-01-08T09:49:23.589Z' beforeAll(() => { @@ -21,8 +26,6 @@ describe('KonnectorErrors', () => { save: jest.fn() } - const mockHistory = {} - const DEFAULT_INSTALLED_KONNECTORS = [ { slug: 'test', name: 'Test Konnector' } ] @@ -38,7 +41,6 @@ describe('KonnectorErrors', () => { triggersInError={triggersInError} accountsWithErrors={accountsWithErrors} installedKonnectors={installedKonnectors} - history={mockHistory} /> ) diff --git a/src/components/KonnectorTile.jsx b/src/components/KonnectorTile.jsx index 6c4b0865ab..424a4352e7 100644 --- a/src/components/KonnectorTile.jsx +++ b/src/components/KonnectorTile.jsx @@ -1,20 +1,19 @@ +import PropTypes from 'prop-types' import React from 'react' +import { NavLink } from 'react-router-dom' import { connect } from 'react-redux' -import { NavLink, withRouter } from 'react-router-dom' -import PropTypes from 'prop-types' -import flag from 'cozy-flags' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' import SquareAppIcon from 'cozy-ui/transpiled/react/SquareAppIcon' - +import flag from 'cozy-flags' import { getErrorLocaleBound, KonnectorJobError } from 'cozy-harvest-lib' +import { useI18n } from 'cozy-ui/transpiled/react/I18n' +import { getKonnectorTriggersCount } from 'reducers' import { getFirstError, getFirstUserError, getLastSyncDate } from 'ducks/connections' -import { getKonnectorTriggersCount } from 'reducers' const getKonnectorError = ({ error, lang, konnector }) => { if (!error || !error.message) { @@ -51,8 +50,7 @@ export const getKonnectorStatus = ({ export const KonnectorTile = props => { const { lang } = useI18n() - const { accountsCount, error, isInMaintenance, userError, konnector, route } = - props + const { accountsCount, error, isInMaintenance, userError, konnector } = props const hideKonnectorErrors = flag('home.konnectors.hide-errors') // flag used for some demo instances where we want to ignore all konnector errors @@ -68,7 +66,7 @@ export const KonnectorTile = props => { return ( @@ -85,19 +83,21 @@ export const KonnectorTile = props => { KonnectorTile.propTypes = { accountsCount: PropTypes.number, error: PropTypes.object, - userError: PropTypes.object, - konnector: PropTypes.object + isInMaintenance: PropTypes.bool, + konnector: PropTypes.object, + userError: PropTypes.object } const mapStateToProps = (state, props) => { const { konnector } = props + return { + accountsCount: getKonnectorTriggersCount(state, konnector), // /!\ error can also be a userError. error: getFirstError(state.connections, konnector.slug), - userError: getFirstUserError(state.connections, konnector.slug), lastSyncDate: getLastSyncDate(state.connections, konnector.slug), - accountsCount: getKonnectorTriggersCount(state, konnector) + userError: getFirstUserError(state.connections, konnector.slug) } } -export default connect(mapStateToProps)(withRouter(KonnectorTile)) +export default connect(mapStateToProps)(KonnectorTile) diff --git a/src/containers/AccountConnection.jsx b/src/containers/AccountConnection.jsx index 97b80fb327..14ca47dcf2 100644 --- a/src/containers/AccountConnection.jsx +++ b/src/containers/AccountConnection.jsx @@ -1,21 +1,20 @@ +import PropTypes from 'prop-types' import React, { Component } from 'react' import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom' -import PropTypes from 'prop-types' import { translate } from 'cozy-ui/transpiled/react/I18n' import KonnectorInstall from 'components/KonnectorInstall' import KonnectorMaintenance from 'components/KonnectorMaintenance' import UpdateMessage from 'components/Banners/UpdateMessage' +import styles from 'styles/accountConnection.styl' +import { isKonnectorUpdateNeededError } from 'lib/konnectors' import { enqueueConnection, getConnectionError, isConnectionConnected, isConnectionEnqueued } from 'ducks/connections' -import { isKonnectorUpdateNeededError } from 'lib/konnectors' -import styles from 'styles/accountConnection.styl' class AccountConnection extends Component { constructor(props, context) { @@ -95,16 +94,16 @@ class AccountConnection extends Component { render() { const { createdAccount, + error, handleConnectionSuccess, konnector, - error, + lang, onCancel, onDone, queued, - t, - lang, success, - successButtonLabel + successButtonLabel, + t } = this.props const { connectionError, oAuthError, maintenance } = this.state const successMessages = @@ -149,9 +148,9 @@ AccountConnection.contextTypes = { } const mapStateToProps = (state, ownProps) => ({ - success: isConnectionConnected(state.connections, ownProps.trigger), error: getConnectionError(state.connections, ownProps.trigger), - queued: isConnectionEnqueued(state.connections, ownProps.trigger) + queued: isConnectionEnqueued(state.connections, ownProps.trigger), + success: isConnectionConnected(state.connections, ownProps.trigger) }) const mapDispatchToProps = dispatch => { @@ -163,4 +162,4 @@ const mapDispatchToProps = dispatch => { export default connect( mapStateToProps, mapDispatchToProps -)(withRouter(translate()(AccountConnection))) +)(translate()(AccountConnection)) diff --git a/src/containers/App.jsx b/src/containers/App.jsx index 89353cc5b8..984961abd6 100644 --- a/src/containers/App.jsx +++ b/src/containers/App.jsx @@ -1,30 +1,31 @@ import React, { useEffect, useState } from 'react' -import { Redirect, Route, Switch, withRouter } from 'react-router-dom' +import { Navigate, Route, Routes } from 'react-router-dom' -import { Q, withClient } from 'cozy-client' import flag, { enable as enableFlags } from 'cozy-flags' import minilog from '@cozy/minilog' +import { Q, useClient } from 'cozy-client' import { useWebviewIntent } from 'cozy-intent' import Alerter from 'cozy-ui/transpiled/react/Alerter' import IconSprite from 'cozy-ui/transpiled/react/Icon/Sprite' -import { Main } from 'cozy-ui/transpiled/react/Layout' import Spinner from 'cozy-ui/transpiled/react/Spinner' +import { Main } from 'cozy-ui/transpiled/react/Layout' -import appEntryPoint from 'components/appEntryPoint' -import MoveModal from 'components/MoveModal' -import HeroHeader from 'components/HeroHeader' +import AddButton from 'components/AddButton/AddButton' import Corner from 'components/HeroHeader/Corner' import Failure from 'components/Failure' +import HeroHeader from 'components/HeroHeader' import Home from 'components/Home' import IntentRedirect from 'components/IntentRedirect' +import MoveModal from 'components/MoveModal' import StoreRedirection from 'components/StoreRedirection' -import { MainView } from 'components/MainView' -import { toFlagNames } from './toFlagNames' -import { BackgroundContainer } from 'components/BackgroundContainer' +import appEntryPoint from 'components/appEntryPoint' import useBreakpoints from 'cozy-ui/transpiled/react/hooks/useBreakpoints' -import AddButton from 'components/AddButton/AddButton' +import { BackgroundContainer } from 'components/BackgroundContainer' import { FLAG_FAB_BUTTON_ENABLED } from 'components/AddButton/helpers' +import { MainView } from 'components/MainView' +import { toFlagNames } from './toFlagNames' +import { Konnector } from 'components/Konnector' const IDLE = 'idle' const FETCHING_CONTEXT = 'FETCHING_CONTEXT' @@ -32,7 +33,8 @@ const FETCHING_CONTEXT = 'FETCHING_CONTEXT' window.flag = window.flag || flag window.minilog = minilog -const App = ({ client, accounts, konnectors, triggers }) => { +const App = ({ accounts, konnectors, triggers }) => { + const client = useClient() const { isMobile } = useBreakpoints() const [status, setStatus] = useState(IDLE) const [contentWrapper, setContentWrapper] = useState(undefined) @@ -119,26 +121,27 @@ const App = ({ client, accounts, konnectors, triggers }) => { )} {!isFetching && ( - - } - /> + ( + element={ setAppsReady(true)} /> - )} - /> - - - - - + } + > + } /> + + }> + } /> + + + + } /> + + } /> + )} @@ -148,8 +151,4 @@ const App = ({ client, accounts, konnectors, triggers }) => { ) } -/* -withRouter is necessary here to deal with redux -https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/blocked-updates.md -*/ -export default withClient(withRouter(appEntryPoint(App))) +export default appEntryPoint(App) diff --git a/src/containers/App.spec.jsx b/src/containers/App.spec.jsx index 4f03ad349e..164bfe8f3d 100644 --- a/src/containers/App.spec.jsx +++ b/src/containers/App.spec.jsx @@ -5,6 +5,7 @@ import App from '../components/AnimatedWrapper' import AppLike from 'test/AppLike' jest.mock('lib/redux-cozy-client/connect.jsx') + jest.mock('lib/redux-cozy-client', () => ({ cozyConnect: () => App => { return () => @@ -23,24 +24,8 @@ jest.mock('lib/redux-cozy-client', () => ({ } })) -jest.mock('cozy-device-helper', () => ({ - isFlagshipApp: jest.fn(), - isAndroidApp: jest.fn(), - isIOS: jest.fn() -})) - // eslint-disable-next-line react/display-name jest.mock('components/HeroHeader', () => () =>
) -// jest.mock('components/Home', () => () =>
) -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - Route: ({ path, exact }) => ( -
- ), - Redirect: ({ children }) =>
{children}
, - Switch: ({ children }) =>
{children}
, - withRouter: children => children -})) jest.mock('cozy-device-helper', () => ({ isFlagshipApp: jest.fn(), @@ -51,6 +36,28 @@ jest.mock('cozy-device-helper', () => ({ isIOS: jest.fn() })) +jest.mock('cozy-harvest-lib', () => ({ + Routes: ({ konnector, triggers, onDismiss }) => ( +
+ {konnector.slug} +
+ ) +})) + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + // eslint-disable-next-line react/display-name + Routes: jest.fn(), + // eslint-disable-next-line react/display-name + withRouter: Component => props => +})) + +jest.mock('react-router', () => ({ + ...jest.requireActual('react-router'), + // eslint-disable-next-line react/display-name + withRouter: Component => props => +})) + describe('App', () => { it('should keep backgroundImage fixed on Flagship app scroll', () => { // Given