Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wip
Browse files Browse the repository at this point in the history
mmeissonnier-pass committed Jan 3, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 7302a0c commit 1c71d61
Showing 20 changed files with 255 additions and 204 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -317,6 +317,7 @@ module.exports = {
['types', './src/types'],
['ui', './src/ui'],
['web', './src/web'],
['events', './src/events'],
],
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.mjs'],
},
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -132,6 +132,7 @@
"date-fns-tz": "^2.0.0",
"deprecated-react-native-prop-types": "^5.0.0",
"design-system": "https://github.com/pass-culture/design-system.git#v0.0.14",
"eventemitter3": "^5.0.1",
"firebase": "^9.6.11",
"geojson": "^0.5.0",
"globalthis": "^1.0.2",
112 changes: 71 additions & 41 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { FunctionComponent, useEffect } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import 'react-native-gesture-handler' // @react-navigation
import 'react-native-get-random-values' // required for `uuid` module to work
import { LogBox, Platform, StatusBar } from 'react-native'
import CodePush from 'react-native-code-push'
import { onlineManager } from 'react-query'

import 'react-native-get-random-values' // required for `uuid` module to work
import 'react-native-gesture-handler' // @react-navigation
// if __DEV__ import if you want to debug
// import './why-did-you-render'
if (process.env.NODE_ENV === 'development') {
@@ -13,6 +14,14 @@ if (process.env.NODE_ENV === 'development') {
import 'intl'
import 'intl/locale-data/jsonp/en'

import { eventBus } from 'events/eventBus'
import { EventBusProvider } from 'events/EventBusProvider'
import {
NAVIGATION_EVENTS,
NETWORK_EVENTS,
SNACKBAR_EVENTS,
SPLASHSCREEN_EVENTS,
} from 'events/eventNames'
import { AccessibilityFiltersWrapper } from 'features/accessibility/context/AccessibilityFiltersWrapper'
import { AuthWrapper } from 'features/auth/context/AuthWrapper'
import { SettingsWrapper } from 'features/auth/context/SettingsContext'
@@ -81,53 +90,55 @@ const App: FunctionComponent = function () {
}, [])

return (
<SplashScreenProvider>
<NetInfoWrapper>
<EventBusProvider>
<SplashScreenProvider>
<RemoteConfigProvider>
<ReactQueryClientProvider>
<ThemeProvider theme={theme}>
<SafeAreaProvider>
<ErrorBoundary FallbackComponent={AsyncErrorBoundaryWithoutNavigation}>
<AnalyticsInitializer>
<SettingsWrapper>
<AuthWrapper>
<LocationWrapper>
<AccessibilityFiltersWrapper>
<FavoritesWrapper>
<SearchAnalyticsWrapper>
<SearchWrapper>
<SnackBarProvider>
<CulturalSurveyContextProvider>
<SubscriptionContextProvider>
<PushNotificationsWrapper>
<ShareAppWrapper>
<OnboardingWrapper>
<OfflineModeContainer>
<ScreenErrorProvider>
<AppNavigationContainer />
</ScreenErrorProvider>
</OfflineModeContainer>
</OnboardingWrapper>
</ShareAppWrapper>
</PushNotificationsWrapper>
</SubscriptionContextProvider>
</CulturalSurveyContextProvider>
</SnackBarProvider>
</SearchWrapper>
</SearchAnalyticsWrapper>
</FavoritesWrapper>
</AccessibilityFiltersWrapper>
</LocationWrapper>
</AuthWrapper>
</SettingsWrapper>
</AnalyticsInitializer>
</ErrorBoundary>
<SnackBarProvider>
<ErrorBoundary FallbackComponent={AsyncErrorBoundaryWithoutNavigation}>
<NetInfoWrapper>
<AnalyticsInitializer>
<SettingsWrapper>
<AuthWrapper>
<LocationWrapper>
<AccessibilityFiltersWrapper>
<FavoritesWrapper>
<SearchAnalyticsWrapper>
<SearchWrapper>
<CulturalSurveyContextProvider>
<SubscriptionContextProvider>
<PushNotificationsWrapper>
<ShareAppWrapper>
<OnboardingWrapper>
<OfflineModeContainer>
<ScreenErrorProvider>
<AppNavigationContainer />
</ScreenErrorProvider>
</OfflineModeContainer>
</OnboardingWrapper>
</ShareAppWrapper>
</PushNotificationsWrapper>
</SubscriptionContextProvider>
</CulturalSurveyContextProvider>
</SearchWrapper>
</SearchAnalyticsWrapper>
</FavoritesWrapper>
</AccessibilityFiltersWrapper>
</LocationWrapper>
</AuthWrapper>
</SettingsWrapper>
</AnalyticsInitializer>
</NetInfoWrapper>
</ErrorBoundary>
</SnackBarProvider>
</SafeAreaProvider>
</ThemeProvider>
</ReactQueryClientProvider>
</RemoteConfigProvider>
</NetInfoWrapper>
</SplashScreenProvider>
</SplashScreenProvider>
</EventBusProvider>
)
}

@@ -139,6 +150,25 @@ const AppWithMonitoring = eventMonitoring.wrap(AppWithoutMonitoring) as React.Co
}>
const AppWithCodepush = __DEV__ ? AppWithMonitoring : CodePush(config)(AppWithMonitoring)

// SIDE EFFECTS
eventBus.on(NETWORK_EVENTS.OFFLINE, () => {
onlineManager.setOnline(false)
eventBus.emit(SPLASHSCREEN_EVENTS.HIDE)
eventBus.emit(SNACKBAR_EVENTS.SHOW, {
type: 'info',
message: 'Aucune connexion internet. Réessaie plus tard',
})
})

eventBus.on(NETWORK_EVENTS.ONLINE, () => {
onlineManager.setOnline(true)
eventBus.emit(SNACKBAR_EVENTS.HIDE)
})

eventBus.once(NAVIGATION_EVENTS.READY, () => {
eventBus.emit(SPLASHSCREEN_EVENTS.HIDE)
})

/**
* We have an import bug in the test file App.native.test.tsx with the new eventMonitoring wrapper : WEIRD !!! :
* Element type is invalid: expected a string (for built-in components) or a class/function (for composite components)
4 changes: 4 additions & 0 deletions src/events/EventBusContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import EventEmitter from 'eventemitter3'
import React from 'react'

export const EventBusContext = React.createContext<EventEmitter>(new EventEmitter())
8 changes: 8 additions & 0 deletions src/events/EventBusProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React, { PropsWithChildren } from 'react'

import { eventBus } from './eventBus'
import { EventBusContext } from './EventBusContext'

export const EventBusProvider = ({ children }: PropsWithChildren) => {
return <EventBusContext.Provider value={eventBus}>{children}</EventBusContext.Provider>
}
3 changes: 3 additions & 0 deletions src/events/eventBus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import EventEmitter from 'eventemitter3'

export const eventBus = new EventEmitter()
18 changes: 18 additions & 0 deletions src/events/eventNames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const SPLASHSCREEN_EVENTS = {
HIDE: 'splashscreen.hide',
SHOW: 'splashscreen.show',
}

export const NETWORK_EVENTS = {
ONLINE: 'network.online',
OFFLINE: 'network.offline',
}

export const SNACKBAR_EVENTS = {
SHOW: 'snackbar.show',
HIDE: 'snackbar.hide',
}

export const NAVIGATION_EVENTS = {
READY: 'navigation.ready',
}
4 changes: 4 additions & 0 deletions src/events/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type SnackBarEventPayload = {
type: 'info' | 'success' | 'error'
message: string
}
5 changes: 5 additions & 0 deletions src/events/useEventBus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useContext } from 'react'

import { EventBusContext } from './EventBusContext'

export const useEventBus = () => useContext(EventBusContext)
27 changes: 15 additions & 12 deletions src/features/auth/context/AuthContext.native.test.tsx
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import React from 'react'

import { BatchProfile } from '__mocks__/@batch.com/react-native-plugin'
import * as jwt from '__mocks__/jwt-decode'
import { UserProfileResponse } from 'api/gen'
import { RefreshResponse, UserProfileResponse } from 'api/gen'
import { CURRENT_DATE } from 'features/auth/fixtures/fixtures'
import * as NavigationRef from 'features/navigation/navigationRef'
import { beneficiaryUser, nonBeneficiaryUser } from 'fixtures/user'
@@ -12,8 +12,7 @@ import { amplitude } from 'libs/amplitude'
import { decodedTokenWithRemainingLifetime, tokenRemainingLifetimeInMs } from 'libs/jwt/fixtures'
import { clearRefreshToken, getRefreshToken, saveRefreshToken } from 'libs/keychain/keychain'
import { eventMonitoring } from 'libs/monitoring'
import { NetInfoWrapper } from 'libs/network/NetInfoWrapper'
import { useNetInfo } from 'libs/network/useNetInfo'
import { useNetInfoContext as useNetInfoContextDefault } from 'libs/network/NetInfoWrapper'
import * as PackageJson from 'libs/packageJson'
import { QueryKeys } from 'libs/queryKeys'
import { StorageKey, storage } from 'libs/storage'
@@ -24,7 +23,8 @@ import { act, renderHook } from 'tests/utils'
import { useAuthContext } from './AuthContext'
import { AuthWrapper } from './AuthWrapper'

const mockedUseNetInfo = useNetInfo as jest.Mock
jest.mock('libs/network/NetInfoWrapper')
const mockUseNetInfoContext = useNetInfoContextDefault as jest.Mock

jest.mock('libs/amplitude/amplitude')

@@ -54,17 +54,25 @@ describe('AuthContext', () => {
describe('useAuthContext', () => {
beforeEach(() => {
mockServer.getApi<UserProfileResponse>('/v1/me', nonBeneficiaryUser)
mockServer.postApi<RefreshResponse>('/v1/refresh_access_token', {})
mockUseNetInfoContext.mockReturnValue({
isConnected: true,
isInternetReachable: true,
})
})

it('should not return user when logged in but no internet connection', async () => {
mockedUseNetInfo.mockReturnValueOnce({ isConnected: false, isInternetReachable: false })
mockUseNetInfoContext.mockReturnValueOnce({
isConnected: false,
isInternetReachable: false,
})
await saveRefreshToken('token')

const result = renderUseAuthContext()

await act(async () => {})

expect(result.current).toBeNull()
expect(result.current.user).toEqual({})
})

it('should return the user when logged in with internet connection', async () => {
@@ -278,12 +286,7 @@ describe('AuthContext', () => {

const renderUseAuthContext = () => {
const { result } = renderHook(useAuthContext, {
wrapper: ({ children }) =>
reactQueryProviderHOC(
<NetInfoWrapper>
<AuthWrapper>{children}</AuthWrapper>
</NetInfoWrapper>
),
wrapper: ({ children }) => reactQueryProviderHOC(<AuthWrapper>{children}</AuthWrapper>),
})

return result
28 changes: 14 additions & 14 deletions src/features/navigation/NavigationContainer/NavigationContainer.tsx
Original file line number Diff line number Diff line change
@@ -8,9 +8,11 @@ import React, { useEffect, useState } from 'react'
import { Platform } from 'react-native'
import { DefaultTheme, useTheme } from 'styled-components/native'

import { NAVIGATION_EVENTS } from 'events/eventNames'
import { useEventBus } from 'events/useEventBus'
import { RootNavigator } from 'features/navigation/RootNavigator'
import { linking } from 'features/navigation/RootNavigator/linking'
import { useSplashScreenContext } from 'libs/splashscreen'
import { useInitialScreen } from 'features/navigation/RootNavigator/useInitialScreenConfig'
import { storage } from 'libs/storage'
import { LoadingPage } from 'ui/components/LoadingPage'

@@ -33,12 +35,15 @@ const DOCUMENT_TITLE_OPTIONS: DocumentTitleOptions = {
}

export const AppNavigationContainer = () => {
const { hideSplashScreen } = useSplashScreenContext()
const theme = useTheme()
const eventBus = useEventBus()

const [isNavReady, setIsNavReady] = useState(false)
const [navigationStateLoaded, setNavigationStateLoaded] = useState(false)
const [initialNavigationState, setInitialNavigationState] = useState<NavigationState>()

const initialScreen = useInitialScreen()

useEffect(() => {
async function restoreNavStateOnReload() {
try {
@@ -50,34 +55,29 @@ export const AppNavigationContainer = () => {

setInitialNavigationState(savedState)
} finally {
setIsNavReady(true)
setNavigationStateLoaded(true)
}
}
restoreNavStateOnReload()
}, [])

useEffect(() => {
if (isNavReady) {
hideSplashScreen?.()
}
}, [isNavReady, hideSplashScreen])

if (!isNavReady) {
return <LoadingPage />
if (isNavReady && navigationStateLoaded && initialScreen) {
eventBus.emit(NAVIGATION_EVENTS.READY)
}

return (
return initialScreen ? (
<NavigationContainer
linking={linking}
initialState={initialNavigationState}
onStateChange={onNavigationStateChange}
onReady={() => setIsNavReady(true)}
fallback={<LoadingPage />}
ref={navigationRef}
documentTitle={DOCUMENT_TITLE_OPTIONS}
theme={getNavThemeConfig(theme)}>
<RootNavigator />
<RootNavigator initialScreen={initialScreen} />
</NavigationContainer>
)
) : null
}

export default AppNavigationContainer
Loading

0 comments on commit 1c71d61

Please sign in to comment.