one 1.1.473 → 1.1.475
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Root.cjs +5 -1
- package/dist/cjs/Root.js +2 -2
- package/dist/cjs/Root.js.map +1 -1
- package/dist/cjs/Root.native.js +5 -2
- package/dist/cjs/Root.native.js.map +2 -2
- package/dist/cjs/createApp.cjs +2 -0
- package/dist/cjs/createApp.js +16 -2
- package/dist/cjs/createApp.js.map +1 -1
- package/dist/cjs/createApp.native.js +1 -0
- package/dist/cjs/createApp.native.js.map +2 -2
- package/dist/cjs/fork/__tests__/getPathFromState.test.cjs +1440 -0
- package/dist/cjs/fork/__tests__/getPathFromState.test.js +1559 -0
- package/dist/cjs/fork/__tests__/getPathFromState.test.js.map +6 -0
- package/dist/cjs/fork/__tests__/getPathFromState.test.native.js +1726 -0
- package/dist/cjs/fork/__tests__/getPathFromState.test.native.js.map +6 -0
- package/dist/cjs/fork/__tests__/getStateFromPath.test.cjs +2565 -0
- package/dist/cjs/fork/__tests__/getStateFromPath.test.js +2702 -0
- package/dist/cjs/fork/__tests__/getStateFromPath.test.js.map +6 -0
- package/dist/cjs/fork/__tests__/getStateFromPath.test.native.js +2861 -0
- package/dist/cjs/fork/__tests__/getStateFromPath.test.native.js.map +6 -0
- package/dist/cjs/fork/getPathFromState.cjs +2 -1
- package/dist/cjs/fork/getPathFromState.js +1 -1
- package/dist/cjs/fork/getPathFromState.js.map +1 -1
- package/dist/cjs/fork/getPathFromState.native.js +10 -5
- package/dist/cjs/fork/getPathFromState.native.js.map +1 -1
- package/dist/cjs/fork/getPathFromState.test.cjs +113 -0
- package/dist/cjs/fork/getPathFromState.test.js +122 -0
- package/dist/cjs/fork/getPathFromState.test.js.map +6 -0
- package/dist/cjs/fork/getPathFromState.test.native.js +135 -0
- package/dist/cjs/fork/getPathFromState.test.native.js.map +6 -0
- package/dist/cjs/fork/getStateFromPath.test.cjs +229 -0
- package/dist/cjs/fork/getStateFromPath.test.js +290 -0
- package/dist/cjs/fork/getStateFromPath.test.js.map +6 -0
- package/dist/cjs/fork/getStateFromPath.test.native.js +374 -0
- package/dist/cjs/fork/getStateFromPath.test.native.js.map +6 -0
- package/dist/cjs/render.cjs +1 -1
- package/dist/cjs/render.js +1 -1
- package/dist/cjs/render.js.map +1 -1
- package/dist/cjs/router/FlagsContext.cjs +27 -0
- package/dist/cjs/router/FlagsContext.js +22 -0
- package/dist/cjs/router/FlagsContext.js.map +6 -0
- package/dist/cjs/router/FlagsContext.native.js +26 -0
- package/dist/cjs/router/FlagsContext.native.js.map +6 -0
- package/dist/cjs/router/getRoutes.cjs +11 -1
- package/dist/cjs/router/getRoutes.js +11 -1
- package/dist/cjs/router/getRoutes.js.map +1 -1
- package/dist/cjs/router/getRoutes.native.js +11 -1
- package/dist/cjs/router/getRoutes.native.js.map +2 -2
- package/dist/cjs/router/matchers.test.cjs +38 -0
- package/dist/cjs/router/matchers.test.js +42 -0
- package/dist/cjs/router/matchers.test.js.map +6 -0
- package/dist/cjs/router/matchers.test.native.js +39 -0
- package/dist/cjs/router/matchers.test.native.js.map +6 -0
- package/dist/cjs/router/router.cjs +3 -35
- package/dist/cjs/router/router.js +2 -26
- package/dist/cjs/router/router.js.map +1 -1
- package/dist/cjs/router/router.native.js +2 -33
- package/dist/cjs/router/router.native.js.map +2 -2
- package/dist/cjs/router/utils/getNavigateAction.cjs +61 -0
- package/dist/cjs/router/utils/getNavigateAction.js +46 -0
- package/dist/cjs/router/utils/getNavigateAction.js.map +6 -0
- package/dist/cjs/router/utils/getNavigateAction.native.js +58 -0
- package/dist/cjs/router/utils/getNavigateAction.native.js.map +6 -0
- package/dist/cjs/router/utils/getNavigateAction.test.cjs +259 -0
- package/dist/cjs/router/utils/getNavigateAction.test.js +295 -0
- package/dist/cjs/router/utils/getNavigateAction.test.js.map +6 -0
- package/dist/cjs/router/utils/getNavigateAction.test.native.js +330 -0
- package/dist/cjs/router/utils/getNavigateAction.test.native.js.map +6 -0
- package/dist/cjs/testing-utils.cjs +63 -0
- package/dist/cjs/testing-utils.js +55 -0
- package/dist/cjs/testing-utils.js.map +6 -0
- package/dist/cjs/testing-utils.native.js +79 -0
- package/dist/cjs/testing-utils.native.js.map +6 -0
- package/dist/cjs/views/Navigator.cjs +8 -1
- package/dist/cjs/views/Navigator.js +25 -11
- package/dist/cjs/views/Navigator.js.map +1 -1
- package/dist/cjs/views/Navigator.native.js +7 -4
- package/dist/cjs/views/Navigator.native.js.map +2 -2
- package/dist/cjs/vite/one.cjs +6 -1
- package/dist/cjs/vite/one.js +6 -1
- package/dist/cjs/vite/one.js.map +1 -1
- package/dist/cjs/vite/one.native.js +7 -2
- package/dist/cjs/vite/one.native.js.map +2 -2
- package/dist/cjs/vite/plugins/virtualEntryPlugin.cjs +2 -0
- package/dist/cjs/vite/plugins/virtualEntryPlugin.js +2 -0
- package/dist/cjs/vite/plugins/virtualEntryPlugin.js.map +1 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js +2 -0
- package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js.map +2 -2
- package/dist/esm/Root.js +2 -1
- package/dist/esm/Root.js.map +1 -1
- package/dist/esm/Root.mjs +5 -1
- package/dist/esm/Root.mjs.map +1 -1
- package/dist/esm/Root.native.js +5 -1
- package/dist/esm/Root.native.js.map +1 -1
- package/dist/esm/createApp.js +16 -2
- package/dist/esm/createApp.js.map +1 -1
- package/dist/esm/createApp.mjs +2 -0
- package/dist/esm/createApp.mjs.map +1 -1
- package/dist/esm/createApp.native.js +1 -0
- package/dist/esm/createApp.native.js.map +1 -1
- package/dist/esm/fork/__tests__/getPathFromState.test.js +1561 -0
- package/dist/esm/fork/__tests__/getPathFromState.test.js.map +6 -0
- package/dist/esm/fork/__tests__/getPathFromState.test.mjs +1441 -0
- package/dist/esm/fork/__tests__/getPathFromState.test.mjs.map +1 -0
- package/dist/esm/fork/__tests__/getPathFromState.test.native.js +1580 -0
- package/dist/esm/fork/__tests__/getPathFromState.test.native.js.map +1 -0
- package/dist/esm/fork/__tests__/getStateFromPath.test.js +2706 -0
- package/dist/esm/fork/__tests__/getStateFromPath.test.js.map +6 -0
- package/dist/esm/fork/__tests__/getStateFromPath.test.mjs +2566 -0
- package/dist/esm/fork/__tests__/getStateFromPath.test.mjs.map +1 -0
- package/dist/esm/fork/__tests__/getStateFromPath.test.native.js +2636 -0
- package/dist/esm/fork/__tests__/getStateFromPath.test.native.js.map +1 -0
- package/dist/esm/fork/getPathFromState.js +1 -1
- package/dist/esm/fork/getPathFromState.js.map +1 -1
- package/dist/esm/fork/getPathFromState.mjs +2 -1
- package/dist/esm/fork/getPathFromState.mjs.map +1 -1
- package/dist/esm/fork/getPathFromState.native.js +9 -5
- package/dist/esm/fork/getPathFromState.native.js.map +1 -1
- package/dist/esm/fork/getPathFromState.test.js +123 -0
- package/dist/esm/fork/getPathFromState.test.js.map +6 -0
- package/dist/esm/fork/getPathFromState.test.mjs +114 -0
- package/dist/esm/fork/getPathFromState.test.mjs.map +1 -0
- package/dist/esm/fork/getPathFromState.test.native.js +122 -0
- package/dist/esm/fork/getPathFromState.test.native.js.map +1 -0
- package/dist/esm/fork/getStateFromPath.test.js +294 -0
- package/dist/esm/fork/getStateFromPath.test.js.map +6 -0
- package/dist/esm/fork/getStateFromPath.test.mjs +230 -0
- package/dist/esm/fork/getStateFromPath.test.mjs.map +1 -0
- package/dist/esm/fork/getStateFromPath.test.native.js +233 -0
- package/dist/esm/fork/getStateFromPath.test.native.js.map +1 -0
- package/dist/esm/render.js +1 -1
- package/dist/esm/render.js.map +1 -1
- package/dist/esm/render.mjs +1 -1
- package/dist/esm/render.mjs.map +1 -1
- package/dist/esm/router/FlagsContext.js +6 -0
- package/dist/esm/router/FlagsContext.js.map +6 -0
- package/dist/esm/router/FlagsContext.mjs +4 -0
- package/dist/esm/router/FlagsContext.mjs.map +1 -0
- package/dist/esm/router/FlagsContext.native.js +4 -0
- package/dist/esm/router/FlagsContext.native.js.map +1 -0
- package/dist/esm/router/getRoutes.js +11 -1
- package/dist/esm/router/getRoutes.js.map +1 -1
- package/dist/esm/router/getRoutes.mjs +11 -1
- package/dist/esm/router/getRoutes.mjs.map +1 -1
- package/dist/esm/router/getRoutes.native.js +11 -1
- package/dist/esm/router/getRoutes.native.js.map +1 -1
- package/dist/esm/router/matchers.test.js +50 -0
- package/dist/esm/router/matchers.test.js.map +6 -0
- package/dist/esm/router/matchers.test.mjs +39 -0
- package/dist/esm/router/matchers.test.mjs.map +1 -0
- package/dist/esm/router/matchers.test.native.js +39 -0
- package/dist/esm/router/matchers.test.native.js.map +1 -0
- package/dist/esm/router/router.js +1 -26
- package/dist/esm/router/router.js.map +1 -1
- package/dist/esm/router/router.mjs +1 -33
- package/dist/esm/router/router.mjs.map +1 -1
- package/dist/esm/router/router.native.js +1 -37
- package/dist/esm/router/router.native.js.map +1 -1
- package/dist/esm/router/utils/getNavigateAction.js +32 -0
- package/dist/esm/router/utils/getNavigateAction.js.map +6 -0
- package/dist/esm/router/utils/getNavigateAction.mjs +38 -0
- package/dist/esm/router/utils/getNavigateAction.mjs.map +1 -0
- package/dist/esm/router/utils/getNavigateAction.native.js +42 -0
- package/dist/esm/router/utils/getNavigateAction.native.js.map +1 -0
- package/dist/esm/router/utils/getNavigateAction.test.js +296 -0
- package/dist/esm/router/utils/getNavigateAction.test.js.map +6 -0
- package/dist/esm/router/utils/getNavigateAction.test.mjs +260 -0
- package/dist/esm/router/utils/getNavigateAction.test.mjs.map +1 -0
- package/dist/esm/router/utils/getNavigateAction.test.native.js +273 -0
- package/dist/esm/router/utils/getNavigateAction.test.native.js.map +1 -0
- package/dist/esm/testing-utils.js +33 -0
- package/dist/esm/testing-utils.js.map +6 -0
- package/dist/esm/testing-utils.mjs +27 -0
- package/dist/esm/testing-utils.mjs.map +1 -0
- package/dist/esm/testing-utils.native.js +38 -0
- package/dist/esm/testing-utils.native.js.map +1 -0
- package/dist/esm/views/Navigator.js +29 -11
- package/dist/esm/views/Navigator.js.map +1 -1
- package/dist/esm/views/Navigator.mjs +8 -1
- package/dist/esm/views/Navigator.mjs.map +1 -1
- package/dist/esm/views/Navigator.native.js +8 -2
- package/dist/esm/views/Navigator.native.js.map +1 -1
- package/dist/esm/vite/one.js +6 -1
- package/dist/esm/vite/one.js.map +1 -1
- package/dist/esm/vite/one.mjs +6 -1
- package/dist/esm/vite/one.mjs.map +1 -1
- package/dist/esm/vite/one.native.js +9 -2
- package/dist/esm/vite/one.native.js.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.js +2 -0
- package/dist/esm/vite/plugins/virtualEntryPlugin.js.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.mjs +2 -0
- package/dist/esm/vite/plugins/virtualEntryPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.native.js +2 -0
- package/dist/esm/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
- package/package.json +13 -10
- package/src/Root.tsx +12 -2
- package/src/createApp.native.tsx +8 -2
- package/src/createApp.tsx +18 -3
- package/src/fork/__tests__/README.md +8 -0
- package/src/fork/__tests__/getPathFromState.test.tsx +1809 -0
- package/src/fork/__tests__/getStateFromPath.test.tsx +3188 -0
- package/src/fork/getPathFromState.test.ts +146 -0
- package/src/fork/getPathFromState.ts +1 -1
- package/src/fork/getStateFromPath.test.ts +345 -0
- package/src/render.tsx +3 -3
- package/src/router/FlagsContext.ts +4 -0
- package/src/router/getRoutes.ts +14 -2
- package/src/router/matchers.test.ts +120 -0
- package/src/router/router.ts +1 -113
- package/src/router/utils/getNavigateAction.test.ts +334 -0
- package/src/router/utils/getNavigateAction.ts +120 -0
- package/src/testing-utils.ts +56 -0
- package/src/views/Navigator.tsx +34 -10
- package/src/vite/one.ts +5 -0
- package/src/vite/plugins/virtualEntryPlugin.ts +4 -1
- package/src/vite/types.ts +18 -0
- package/types/Root.d.ts +1 -0
- package/types/Root.d.ts.map +1 -1
- package/types/createApp.d.ts +2 -0
- package/types/createApp.d.ts.map +1 -1
- package/types/createApp.native.d.ts +2 -0
- package/types/createApp.native.d.ts.map +1 -1
- package/types/fork/getPathFromState.test.d.ts +2 -0
- package/types/fork/getPathFromState.test.d.ts.map +1 -0
- package/types/fork/getStateFromPath.test.d.ts +2 -0
- package/types/fork/getStateFromPath.test.d.ts.map +1 -0
- package/types/router/FlagsContext.d.ts +3 -0
- package/types/router/FlagsContext.d.ts.map +1 -0
- package/types/router/getRoutes.d.ts.map +1 -1
- package/types/router/matchers.test.d.ts +2 -0
- package/types/router/matchers.test.d.ts.map +1 -0
- package/types/router/router.d.ts.map +1 -1
- package/types/router/utils/getNavigateAction.d.ts +17 -0
- package/types/router/utils/getNavigateAction.d.ts.map +1 -0
- package/types/router/utils/getNavigateAction.test.d.ts +2 -0
- package/types/router/utils/getNavigateAction.test.d.ts.map +1 -0
- package/types/testing-utils.d.ts +26 -0
- package/types/testing-utils.d.ts.map +1 -0
- package/types/views/Navigator.d.ts +1 -1
- package/types/views/Navigator.d.ts.map +1 -1
- package/types/vite/one.d.ts.map +1 -1
- package/types/vite/plugins/virtualEntryPlugin.d.ts +2 -0
- package/types/vite/plugins/virtualEntryPlugin.d.ts.map +1 -1
- package/types/vite/types.d.ts +16 -0
- package/types/vite/types.d.ts.map +1 -1
package/src/router/router.ts
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
-
import type { NavigationState, PartialRoute } from '@react-navigation/native'
|
2
1
|
import {
|
3
2
|
StackActions,
|
4
3
|
type NavigationContainerRefWithCurrent,
|
5
4
|
type getPathFromState as originalGetPathFromState,
|
6
5
|
} from '@react-navigation/native'
|
7
6
|
import * as Linking from 'expo-linking'
|
8
|
-
import { nanoid } from 'nanoid/non-secure'
|
9
7
|
import { Fragment, startTransition, type ComponentType, useSyncExternalStore } from 'react'
|
10
8
|
import { Platform } from 'react-native'
|
11
9
|
import type { State } from '../fork/getPathFromState'
|
@@ -23,10 +21,10 @@ import { getLinkingConfig, type OneLinkingOptions } from './getLinkingConfig'
|
|
23
21
|
import { getNormalizedStatePath, type UrlObject } from './getNormalizedStatePath'
|
24
22
|
import { getRoutes } from './getRoutes'
|
25
23
|
import { setLastAction } from './lastAction'
|
26
|
-
import { matchDynamicName } from './matchers'
|
27
24
|
import type { RouteNode } from './Route'
|
28
25
|
import { sortRoutes } from './sortRoutes'
|
29
26
|
import { getQualifiedRouteComponent } from './useScreens'
|
27
|
+
import { getNavigateAction } from './utils/getNavigateAction'
|
30
28
|
|
31
29
|
// Module-scoped variables
|
32
30
|
export let routeNode: RouteNode | null = null
|
@@ -549,116 +547,6 @@ const hashes: Record<string, string> = {}
|
|
549
547
|
|
550
548
|
let nextOptions: OneRouter.LinkToOptions | null = null
|
551
549
|
|
552
|
-
function getNavigateAction(
|
553
|
-
actionState: OneRouter.ResultState,
|
554
|
-
navigationState: NavigationState,
|
555
|
-
type = 'NAVIGATE'
|
556
|
-
) {
|
557
|
-
/**
|
558
|
-
* We need to find the deepest navigator where the action and current state diverge, If they do not diverge, the
|
559
|
-
* lowest navigator is the target.
|
560
|
-
*
|
561
|
-
* By default React Navigation will target the current navigator, but this doesn't work for all actions
|
562
|
-
* For example:
|
563
|
-
* - /deeply/nested/route -> /top-level-route the target needs to be the top-level navigator
|
564
|
-
* - /stack/nestedStack/page -> /stack1/nestedStack/other-page needs to target the nestedStack navigator
|
565
|
-
*
|
566
|
-
* This matching needs to done by comparing the route names and the dynamic path, for example
|
567
|
-
* - /1/page -> /2/anotherPage needs to target the /[id] navigator
|
568
|
-
*
|
569
|
-
* Other parameters such as search params and hash are not evaluated.
|
570
|
-
*
|
571
|
-
*/
|
572
|
-
let actionStateRoute: PartialRoute<any> | undefined
|
573
|
-
|
574
|
-
// Traverse the state tree comparing the current state and the action state until we find where they diverge
|
575
|
-
while (actionState && navigationState) {
|
576
|
-
const stateRoute = navigationState.routes[navigationState.index]
|
577
|
-
|
578
|
-
actionStateRoute = actionState.routes[actionState.routes.length - 1]
|
579
|
-
|
580
|
-
const childState = actionStateRoute.state
|
581
|
-
const nextNavigationState = stateRoute.state
|
582
|
-
|
583
|
-
const dynamicName = matchDynamicName(actionStateRoute.name)
|
584
|
-
|
585
|
-
const didActionAndCurrentStateDiverge =
|
586
|
-
actionStateRoute.name !== stateRoute.name ||
|
587
|
-
!childState ||
|
588
|
-
!nextNavigationState ||
|
589
|
-
(dynamicName && actionStateRoute.params?.[dynamicName] !== stateRoute.params?.[dynamicName])
|
590
|
-
|
591
|
-
if (didActionAndCurrentStateDiverge) {
|
592
|
-
break
|
593
|
-
}
|
594
|
-
|
595
|
-
actionState = childState
|
596
|
-
navigationState = nextNavigationState as NavigationState
|
597
|
-
}
|
598
|
-
|
599
|
-
/*
|
600
|
-
* We found the target navigator, but the payload is in the incorrect format
|
601
|
-
* We need to convert the action state to a payload that can be dispatched
|
602
|
-
*/
|
603
|
-
const rootPayload: Record<string, any> = { params: {} }
|
604
|
-
let payload = rootPayload
|
605
|
-
let params = payload.params
|
606
|
-
|
607
|
-
// The root level of payload is a bit weird, its params are in the child object
|
608
|
-
while (actionStateRoute) {
|
609
|
-
Object.assign(params, { ...actionStateRoute.params })
|
610
|
-
payload.screen = actionStateRoute.name
|
611
|
-
payload.params = { ...actionStateRoute.params }
|
612
|
-
|
613
|
-
actionStateRoute = actionStateRoute.state?.routes[actionStateRoute.state?.routes.length - 1]
|
614
|
-
|
615
|
-
payload.params ??= {}
|
616
|
-
payload = payload.params
|
617
|
-
params = payload
|
618
|
-
}
|
619
|
-
|
620
|
-
// One uses only three actions, but these don't directly translate to all navigator actions
|
621
|
-
if (type === 'PUSH') {
|
622
|
-
setLastAction()
|
623
|
-
|
624
|
-
// Only stack navigators have a push action, and even then we want to use NAVIGATE (see below)
|
625
|
-
type = 'NAVIGATE'
|
626
|
-
|
627
|
-
/*
|
628
|
-
* The StackAction.PUSH does not work correctly with One.
|
629
|
-
*
|
630
|
-
* One provides a getId() function for every route, altering how React Navigation handles stack routing.
|
631
|
-
* Ordinarily, PUSH always adds a new screen to the stack. However, with getId() present, it navigates to the screen with the matching ID instead
|
632
|
-
* (by moving the screen to the top of the stack)
|
633
|
-
* When you try and push to a screen with the same ID, no navigation will occur
|
634
|
-
* Refer to: https://github.com/react-navigation/react-navigation/blob/13d4aa270b301faf07960b4cd861ffc91e9b2c46/packages/routers/src/StackRouter.tsx#L279-L290
|
635
|
-
*
|
636
|
-
* One needs to retain the default behavior of PUSH, consistently adding new screens to the stack, even if their IDs are identical.
|
637
|
-
*
|
638
|
-
* To resolve this issue, we switch to using a NAVIGATE action with a new key. In the navigate action, screens are matched by either key or getId() function.
|
639
|
-
* By generating a unique new key, we ensure that the screen is always pushed onto the stack.
|
640
|
-
*
|
641
|
-
*/
|
642
|
-
if (navigationState.type === 'stack') {
|
643
|
-
rootPayload.key = `${rootPayload.name}-${nanoid()}` // @see https://github.com/react-navigation/react-navigation/blob/13d4aa270b301faf07960b4cd861ffc91e9b2c46/packages/routers/src/StackRouter.tsx#L406-L407
|
644
|
-
}
|
645
|
-
}
|
646
|
-
|
647
|
-
if (type === 'REPLACE' && navigationState.type === 'tab') {
|
648
|
-
type = 'JUMP_TO'
|
649
|
-
}
|
650
|
-
|
651
|
-
return {
|
652
|
-
type,
|
653
|
-
target: navigationState.key,
|
654
|
-
payload: {
|
655
|
-
key: rootPayload.key,
|
656
|
-
name: rootPayload.screen,
|
657
|
-
params: rootPayload.params,
|
658
|
-
},
|
659
|
-
}
|
660
|
-
}
|
661
|
-
|
662
550
|
function deepEqual(a: any, b: any) {
|
663
551
|
if (a === b) {
|
664
552
|
return true
|
@@ -0,0 +1,334 @@
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
2
|
+
import { getNavigateAction } from './getNavigateAction'
|
3
|
+
|
4
|
+
describe('getNavigateAction', () => {
|
5
|
+
describe('NAVIGATE', () => {
|
6
|
+
it('works', () => {
|
7
|
+
const actionState = {
|
8
|
+
routes: [
|
9
|
+
{
|
10
|
+
name: 'page-2',
|
11
|
+
},
|
12
|
+
],
|
13
|
+
}
|
14
|
+
|
15
|
+
const navigationState = {
|
16
|
+
stale: false as const,
|
17
|
+
type: 'stack',
|
18
|
+
key: 'stack-pWRo04',
|
19
|
+
index: 0,
|
20
|
+
routeNames: ['_sitemap', 'index', 'page-1', 'page-2'],
|
21
|
+
routes: [
|
22
|
+
{
|
23
|
+
name: 'page-1',
|
24
|
+
key: 'page-1-Gc-TeIdZmx_jAcRD-SGcs',
|
25
|
+
},
|
26
|
+
],
|
27
|
+
preloadedRoutes: [],
|
28
|
+
}
|
29
|
+
|
30
|
+
const action = getNavigateAction(actionState, navigationState)
|
31
|
+
|
32
|
+
expect(action).toStrictEqual({
|
33
|
+
type: 'NAVIGATE',
|
34
|
+
target: 'stack-pWRo04',
|
35
|
+
payload: { key: undefined, name: 'page-2', params: {} },
|
36
|
+
})
|
37
|
+
})
|
38
|
+
|
39
|
+
it('handles params', () => {
|
40
|
+
const actionState = {
|
41
|
+
routes: [
|
42
|
+
{
|
43
|
+
name: 'page-1',
|
44
|
+
params: {
|
45
|
+
foo: 'foo',
|
46
|
+
bar: 'bar',
|
47
|
+
},
|
48
|
+
},
|
49
|
+
],
|
50
|
+
}
|
51
|
+
|
52
|
+
const navigationState = {
|
53
|
+
stale: false as const,
|
54
|
+
type: 'stack',
|
55
|
+
key: 'stack-pWRo04',
|
56
|
+
index: 0,
|
57
|
+
routeNames: ['_sitemap', 'index', 'page-1', 'page-2'],
|
58
|
+
routes: [
|
59
|
+
{
|
60
|
+
name: 'page-1',
|
61
|
+
key: 'page-1-Gc-TeIdZmx_jAcRD-SGcs',
|
62
|
+
},
|
63
|
+
],
|
64
|
+
preloadedRoutes: [],
|
65
|
+
}
|
66
|
+
|
67
|
+
const action = getNavigateAction(actionState, navigationState)
|
68
|
+
|
69
|
+
expect(action).toStrictEqual({
|
70
|
+
type: 'NAVIGATE',
|
71
|
+
target: 'stack-pWRo04',
|
72
|
+
payload: {
|
73
|
+
key: undefined,
|
74
|
+
name: 'page-1',
|
75
|
+
params: {
|
76
|
+
foo: 'foo',
|
77
|
+
bar: 'bar',
|
78
|
+
},
|
79
|
+
},
|
80
|
+
})
|
81
|
+
})
|
82
|
+
|
83
|
+
it('handles navigating into nested navigator', () => {
|
84
|
+
const actionState = {
|
85
|
+
routes: [
|
86
|
+
{
|
87
|
+
name: 'foo',
|
88
|
+
state: {
|
89
|
+
routes: [
|
90
|
+
{
|
91
|
+
name: 'bar',
|
92
|
+
state: {
|
93
|
+
routes: [
|
94
|
+
{
|
95
|
+
name: 'baz',
|
96
|
+
},
|
97
|
+
],
|
98
|
+
},
|
99
|
+
},
|
100
|
+
],
|
101
|
+
},
|
102
|
+
},
|
103
|
+
],
|
104
|
+
}
|
105
|
+
|
106
|
+
const navigationState = {
|
107
|
+
stale: false as const,
|
108
|
+
type: 'stack',
|
109
|
+
key: 'stack-5qQ9ln4FB9',
|
110
|
+
index: 0,
|
111
|
+
routeNames: ['index', 'foo'],
|
112
|
+
routes: [
|
113
|
+
{
|
114
|
+
name: 'index',
|
115
|
+
key: 'index-Kyz4PdQ7ZAvE0XFhBWydM',
|
116
|
+
},
|
117
|
+
],
|
118
|
+
preloadedRoutes: [],
|
119
|
+
}
|
120
|
+
|
121
|
+
const action = getNavigateAction(actionState, navigationState)
|
122
|
+
|
123
|
+
expect(action).toStrictEqual({
|
124
|
+
type: 'NAVIGATE',
|
125
|
+
target: 'stack-5qQ9ln4FB9',
|
126
|
+
payload: {
|
127
|
+
key: undefined,
|
128
|
+
name: 'foo',
|
129
|
+
params: {
|
130
|
+
screen: 'bar',
|
131
|
+
params: {
|
132
|
+
screen: 'baz',
|
133
|
+
params: {},
|
134
|
+
},
|
135
|
+
},
|
136
|
+
},
|
137
|
+
})
|
138
|
+
})
|
139
|
+
|
140
|
+
it('handles navigating into nested navigator with route params', () => {
|
141
|
+
const actionState = {
|
142
|
+
routes: [
|
143
|
+
{
|
144
|
+
name: '[level1]',
|
145
|
+
params: {
|
146
|
+
level1: 'foo',
|
147
|
+
},
|
148
|
+
state: {
|
149
|
+
routes: [
|
150
|
+
{
|
151
|
+
name: '[level2]',
|
152
|
+
params: {
|
153
|
+
level1: 'foo',
|
154
|
+
level2: 'bar',
|
155
|
+
},
|
156
|
+
state: {
|
157
|
+
routes: [
|
158
|
+
{
|
159
|
+
name: '[level3]',
|
160
|
+
params: {
|
161
|
+
level1: 'foo',
|
162
|
+
level2: 'bar',
|
163
|
+
level3: 'baz',
|
164
|
+
},
|
165
|
+
},
|
166
|
+
],
|
167
|
+
},
|
168
|
+
},
|
169
|
+
],
|
170
|
+
},
|
171
|
+
},
|
172
|
+
],
|
173
|
+
}
|
174
|
+
|
175
|
+
const navigationState = {
|
176
|
+
stale: false as const,
|
177
|
+
type: 'stack',
|
178
|
+
key: 'stack-5qQ9ln4FB9',
|
179
|
+
index: 0,
|
180
|
+
routeNames: ['index', '[level1]'],
|
181
|
+
routes: [
|
182
|
+
{
|
183
|
+
name: 'index',
|
184
|
+
key: 'index-Kyz4PdQ7ZAvE0XFhBWydM',
|
185
|
+
},
|
186
|
+
],
|
187
|
+
preloadedRoutes: [],
|
188
|
+
}
|
189
|
+
|
190
|
+
const action = getNavigateAction(actionState, navigationState)
|
191
|
+
|
192
|
+
expect(action).toStrictEqual({
|
193
|
+
type: 'NAVIGATE',
|
194
|
+
target: 'stack-5qQ9ln4FB9',
|
195
|
+
payload: {
|
196
|
+
key: undefined,
|
197
|
+
name: '[level1]',
|
198
|
+
params: {
|
199
|
+
level1: 'foo',
|
200
|
+
level2: 'bar', // not actually necessary, but it's how the current implementation works
|
201
|
+
screen: '[level2]',
|
202
|
+
params: {
|
203
|
+
level1: 'foo',
|
204
|
+
level2: 'bar',
|
205
|
+
level3: 'baz', // not actually necessary, but it's how the current implementation works
|
206
|
+
screen: '[level3]',
|
207
|
+
params: {
|
208
|
+
level1: 'foo',
|
209
|
+
level2: 'bar',
|
210
|
+
level3: 'baz',
|
211
|
+
},
|
212
|
+
},
|
213
|
+
},
|
214
|
+
},
|
215
|
+
})
|
216
|
+
})
|
217
|
+
|
218
|
+
it('correctly finds out where the states diverge and return an action valid payload', () => {
|
219
|
+
const actionState = {
|
220
|
+
routes: [
|
221
|
+
{
|
222
|
+
name: 'foo',
|
223
|
+
state: {
|
224
|
+
routes: [
|
225
|
+
{
|
226
|
+
name: 'bar',
|
227
|
+
state: {
|
228
|
+
routes: [
|
229
|
+
{
|
230
|
+
name: 'baz-2',
|
231
|
+
},
|
232
|
+
],
|
233
|
+
},
|
234
|
+
},
|
235
|
+
],
|
236
|
+
},
|
237
|
+
},
|
238
|
+
],
|
239
|
+
}
|
240
|
+
|
241
|
+
const navigationState = {
|
242
|
+
stale: false as const,
|
243
|
+
type: 'stack',
|
244
|
+
key: 'stack-aCzOliK0',
|
245
|
+
routeNames: ['index', 'foo'],
|
246
|
+
index: 0,
|
247
|
+
routes: [
|
248
|
+
{
|
249
|
+
name: 'foo',
|
250
|
+
key: 'foo-teuUBQHk',
|
251
|
+
state: {
|
252
|
+
stale: false as const,
|
253
|
+
type: 'stack',
|
254
|
+
key: 'stack-XZ3RJRBg',
|
255
|
+
routeNames: ['bar'],
|
256
|
+
index: 0,
|
257
|
+
routes: [
|
258
|
+
{
|
259
|
+
name: 'bar',
|
260
|
+
key: 'bar-FDtH59Dj',
|
261
|
+
state: {
|
262
|
+
stale: false as const,
|
263
|
+
type: 'stack',
|
264
|
+
key: 'stack-s3o7RyPD',
|
265
|
+
routeNames: ['baz-1', 'baz-2'],
|
266
|
+
index: 0,
|
267
|
+
routes: [
|
268
|
+
{
|
269
|
+
name: 'baz-1',
|
270
|
+
key: 'baz-1-K2zLhRSZ',
|
271
|
+
},
|
272
|
+
],
|
273
|
+
},
|
274
|
+
},
|
275
|
+
],
|
276
|
+
},
|
277
|
+
},
|
278
|
+
],
|
279
|
+
preloadedRoutes: [],
|
280
|
+
}
|
281
|
+
|
282
|
+
const action = getNavigateAction(actionState, navigationState)
|
283
|
+
|
284
|
+
expect(action).toStrictEqual({
|
285
|
+
type: 'NAVIGATE',
|
286
|
+
target: 'stack-s3o7RyPD',
|
287
|
+
payload: {
|
288
|
+
key: undefined,
|
289
|
+
name: 'baz-2',
|
290
|
+
params: {},
|
291
|
+
},
|
292
|
+
})
|
293
|
+
})
|
294
|
+
})
|
295
|
+
|
296
|
+
describe('PUSH', () => {
|
297
|
+
it('returns a NAVIGATE action with a unique key', () => {
|
298
|
+
const actionState = {
|
299
|
+
routes: [
|
300
|
+
{
|
301
|
+
name: 'page-2',
|
302
|
+
},
|
303
|
+
],
|
304
|
+
}
|
305
|
+
|
306
|
+
const navigationState = {
|
307
|
+
stale: false as const,
|
308
|
+
type: 'stack',
|
309
|
+
key: 'stack-pWRo04',
|
310
|
+
index: 0,
|
311
|
+
routeNames: ['_sitemap', 'index', 'page-1', 'page-2'],
|
312
|
+
routes: [
|
313
|
+
{
|
314
|
+
name: 'page-1',
|
315
|
+
key: 'page-1-Gc-TeIdZmx_jAcRD-SGcs',
|
316
|
+
},
|
317
|
+
],
|
318
|
+
preloadedRoutes: [],
|
319
|
+
}
|
320
|
+
|
321
|
+
const action = getNavigateAction(actionState, navigationState, 'PUSH')
|
322
|
+
|
323
|
+
expect(action).toStrictEqual({
|
324
|
+
type: 'NAVIGATE',
|
325
|
+
target: 'stack-pWRo04',
|
326
|
+
payload: {
|
327
|
+
key: action.payload.key /* since this contains a randomly generated nanoid */,
|
328
|
+
name: 'page-2',
|
329
|
+
params: {},
|
330
|
+
},
|
331
|
+
})
|
332
|
+
})
|
333
|
+
})
|
334
|
+
})
|
@@ -0,0 +1,120 @@
|
|
1
|
+
import { nanoid } from 'nanoid'
|
2
|
+
import type { NavigationState, PartialRoute } from '@react-navigation/core'
|
3
|
+
import type { OneRouter } from '../../interfaces/router'
|
4
|
+
import { matchDynamicName } from '../matchers'
|
5
|
+
import { setLastAction } from '../lastAction'
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Generates a navigation action to transition from the current state to the desired state.
|
9
|
+
*/
|
10
|
+
export function getNavigateAction(
|
11
|
+
/** desired state */
|
12
|
+
actionState: OneRouter.ResultState,
|
13
|
+
navigationState: NavigationState,
|
14
|
+
type = 'NAVIGATE'
|
15
|
+
) {
|
16
|
+
/**
|
17
|
+
* We need to find the deepest navigator where the action and current state diverge, If they do not diverge, the
|
18
|
+
* lowest navigator is the target.
|
19
|
+
*
|
20
|
+
* By default React Navigation will target the current navigator, but this doesn't work for all actions
|
21
|
+
* For example:
|
22
|
+
* - /deeply/nested/route -> /top-level-route the target needs to be the top-level navigator
|
23
|
+
* - /stack/nestedStack/page -> /stack1/nestedStack/other-page needs to target the nestedStack navigator
|
24
|
+
*
|
25
|
+
* This matching needs to done by comparing the route names and the dynamic path, for example
|
26
|
+
* - /1/page -> /2/anotherPage needs to target the /[id] navigator
|
27
|
+
*
|
28
|
+
* Other parameters such as search params and hash are not evaluated.
|
29
|
+
*
|
30
|
+
*/
|
31
|
+
let actionStateRoute: PartialRoute<any> | undefined
|
32
|
+
|
33
|
+
// Traverse the state tree comparing the current state and the action state until we find where they diverge
|
34
|
+
while (actionState && navigationState) {
|
35
|
+
const stateRoute = navigationState.routes[navigationState.index]
|
36
|
+
|
37
|
+
actionStateRoute = actionState.routes[actionState.routes.length - 1]
|
38
|
+
|
39
|
+
const childState = actionStateRoute.state
|
40
|
+
const nextNavigationState = stateRoute.state
|
41
|
+
|
42
|
+
const dynamicName = matchDynamicName(actionStateRoute.name)
|
43
|
+
|
44
|
+
const didActionAndCurrentStateDiverge =
|
45
|
+
actionStateRoute.name !== stateRoute.name ||
|
46
|
+
// !deepEqual(actionStateRoute.params, stateRoute.params) ||
|
47
|
+
!childState ||
|
48
|
+
!nextNavigationState ||
|
49
|
+
(dynamicName && actionStateRoute.params?.[dynamicName] !== stateRoute.params?.[dynamicName])
|
50
|
+
|
51
|
+
if (didActionAndCurrentStateDiverge) {
|
52
|
+
break
|
53
|
+
}
|
54
|
+
|
55
|
+
actionState = childState
|
56
|
+
navigationState = nextNavigationState as NavigationState
|
57
|
+
}
|
58
|
+
|
59
|
+
/*
|
60
|
+
* We found the target navigator, but the payload is in the incorrect format
|
61
|
+
* We need to convert the action state to a payload that can be dispatched
|
62
|
+
*/
|
63
|
+
const rootPayload: Record<string, any> = { params: {} }
|
64
|
+
let payload = rootPayload
|
65
|
+
let params = payload.params
|
66
|
+
|
67
|
+
// The root level of payload is a bit weird, its params are in the child object
|
68
|
+
while (actionStateRoute) {
|
69
|
+
Object.assign(params, { ...actionStateRoute.params })
|
70
|
+
payload.screen = actionStateRoute.name
|
71
|
+
payload.params = { ...actionStateRoute.params }
|
72
|
+
|
73
|
+
actionStateRoute = actionStateRoute.state?.routes[actionStateRoute.state?.routes.length - 1]
|
74
|
+
|
75
|
+
payload.params ??= {}
|
76
|
+
payload = payload.params
|
77
|
+
params = payload
|
78
|
+
}
|
79
|
+
|
80
|
+
// One uses only three actions, but these don't directly translate to all navigator actions
|
81
|
+
if (type === 'PUSH') {
|
82
|
+
setLastAction()
|
83
|
+
|
84
|
+
// Only stack navigators have a push action, and even then we want to use NAVIGATE (see below)
|
85
|
+
type = 'NAVIGATE'
|
86
|
+
|
87
|
+
/*
|
88
|
+
* The StackAction.PUSH does not work correctly with One.
|
89
|
+
*
|
90
|
+
* One provides a getId() function for every route, altering how React Navigation handles stack routing.
|
91
|
+
* Ordinarily, PUSH always adds a new screen to the stack. However, with getId() present, it navigates to the screen with the matching ID instead
|
92
|
+
* (by moving the screen to the top of the stack)
|
93
|
+
* When you try and push to a screen with the same ID, no navigation will occur
|
94
|
+
* Refer to: https://github.com/react-navigation/react-navigation/blob/13d4aa270b301faf07960b4cd861ffc91e9b2c46/packages/routers/src/StackRouter.tsx#L279-L290
|
95
|
+
*
|
96
|
+
* One needs to retain the default behavior of PUSH, consistently adding new screens to the stack, even if their IDs are identical.
|
97
|
+
*
|
98
|
+
* To resolve this issue, we switch to using a NAVIGATE action with a new key. In the navigate action, screens are matched by either key or getId() function.
|
99
|
+
* By generating a unique new key, we ensure that the screen is always pushed onto the stack.
|
100
|
+
*
|
101
|
+
*/
|
102
|
+
if (navigationState.type === 'stack') {
|
103
|
+
rootPayload.key = `${rootPayload.name}-${nanoid()}` // @see https://github.com/react-navigation/react-navigation/blob/13d4aa270b301faf07960b4cd861ffc91e9b2c46/packages/routers/src/StackRouter.tsx#L406-L407
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
if (type === 'REPLACE' && navigationState.type === 'tab') {
|
108
|
+
type = 'JUMP_TO'
|
109
|
+
}
|
110
|
+
|
111
|
+
return {
|
112
|
+
type,
|
113
|
+
target: navigationState.key,
|
114
|
+
payload: {
|
115
|
+
key: rootPayload.key,
|
116
|
+
name: rootPayload.screen,
|
117
|
+
params: rootPayload.params,
|
118
|
+
},
|
119
|
+
}
|
120
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import path from 'node:path'
|
2
|
+
|
3
|
+
import { getRoutes } from './router/getRoutes'
|
4
|
+
import { getReactNavigationConfig } from './getReactNavigationConfig'
|
5
|
+
|
6
|
+
export type ReactComponent = () => React.ReactElement<any, any> | null
|
7
|
+
export type FileStub =
|
8
|
+
| (Record<string, unknown> & {
|
9
|
+
default: ReactComponent
|
10
|
+
unstable_settings?: Record<string, any>
|
11
|
+
})
|
12
|
+
| ReactComponent
|
13
|
+
export type NativeIntentStub = any
|
14
|
+
export type MemoryContext = Record<string, FileStub | NativeIntentStub> & {
|
15
|
+
'+native-intent'?: NativeIntentStub
|
16
|
+
}
|
17
|
+
|
18
|
+
const validExtensions = ['.js', '.jsx', '.ts', '.tsx']
|
19
|
+
|
20
|
+
export function inMemoryContext(context: MemoryContext) {
|
21
|
+
return Object.assign(
|
22
|
+
(id: string) => {
|
23
|
+
id = id.replace(/^\.\//, '').replace(/\.\w*$/, '')
|
24
|
+
return typeof context[id] === 'function' ? { default: context[id] } : context[id]
|
25
|
+
},
|
26
|
+
{
|
27
|
+
resolve: (key: string) => key,
|
28
|
+
id: '0',
|
29
|
+
keys: () =>
|
30
|
+
Object.keys(context).map((key) => {
|
31
|
+
const ext = path.extname(key)
|
32
|
+
key = key.replace(/^\.\//, '')
|
33
|
+
key = key.startsWith('/') ? key : `./${key}`
|
34
|
+
key = validExtensions.includes(ext) ? key : `${key}.js`
|
35
|
+
|
36
|
+
return key
|
37
|
+
}),
|
38
|
+
}
|
39
|
+
)
|
40
|
+
}
|
41
|
+
|
42
|
+
type MockContextConfig = string[] // Array of filenames to mock as empty components, e.g () => null
|
43
|
+
|
44
|
+
export function getMockContext(context: MockContextConfig) {
|
45
|
+
if (Array.isArray(context)) {
|
46
|
+
return inMemoryContext(
|
47
|
+
Object.fromEntries(context.map((filename) => [filename, { default: () => null }]))
|
48
|
+
)
|
49
|
+
}
|
50
|
+
|
51
|
+
throw new Error('Invalid context')
|
52
|
+
}
|
53
|
+
|
54
|
+
export function getMockConfig(context: MockContextConfig, metaOnly = true) {
|
55
|
+
return getReactNavigationConfig(getRoutes(getMockContext(context))!, metaOnly)
|
56
|
+
}
|