Fix back swipe opening the drawer after a cold-start deep link (#11065)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
buildStateObject,
|
||||
getCurrentRoute,
|
||||
isStateAtTabRoot,
|
||||
} from '#/lib/routes/helpers'
|
||||
import {type State} from '#/lib/routes/types'
|
||||
|
||||
describe('getCurrentRoute', () => {
|
||||
it('returns Home when there is no state', () => {
|
||||
expect(getCurrentRoute(undefined).name).toBe('Home')
|
||||
})
|
||||
|
||||
it('descends into nested state using the index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
name: 'HomeTab',
|
||||
state: {
|
||||
index: 1,
|
||||
routes: [{name: 'Home'}, {name: 'PostThread'}],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as State
|
||||
|
||||
expect(getCurrentRoute(state).name).toBe('PostThread')
|
||||
})
|
||||
|
||||
/*
|
||||
* Right after a cold start from a deep link, nested navigator states are
|
||||
* still partial and have no `index`. React Navigation focuses the last
|
||||
* route when rehydrating such a state, so getCurrentRoute must do the
|
||||
* same (previously it stopped at the tab route, which re-enabled the
|
||||
* drawer swipe gesture on top of the deep-linked screen).
|
||||
*/
|
||||
it('descends into partial nested state without an index', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'HomeTab',
|
||||
state: {
|
||||
routes: [{name: 'Home'}, {name: 'PostThread'}],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as State
|
||||
|
||||
expect(getCurrentRoute(state).name).toBe('PostThread')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isStateAtTabRoot', () => {
|
||||
it('returns true for the initial deep link state of a tab root', () => {
|
||||
const state = buildStateObject('HomeTab', 'Home', {}) as unknown as State
|
||||
expect(isStateAtTabRoot(state)).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false for the initial deep link state of a nested screen', () => {
|
||||
const state = buildStateObject(
|
||||
'HomeTab',
|
||||
'PostThread',
|
||||
{name: 'alice.test', rkey: '123'},
|
||||
[{name: 'Home', params: {}}],
|
||||
) as unknown as State
|
||||
expect(isStateAtTabRoot(state)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildStateObject', () => {
|
||||
it('focuses the deep-linked route in the nested state', () => {
|
||||
const state = buildStateObject(
|
||||
'HomeTab',
|
||||
'PostThread',
|
||||
{name: 'alice.test', rkey: '123'},
|
||||
[{name: 'Home', params: {}}],
|
||||
)
|
||||
|
||||
expect(state).toEqual({
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
name: 'HomeTab',
|
||||
state: {
|
||||
index: 1,
|
||||
routes: [
|
||||
{name: 'Home', params: {}},
|
||||
{name: 'PostThread', params: {name: 'alice.test', rkey: '123'}},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('builds a single-route state for the Flat navigator', () => {
|
||||
const state = buildStateObject('Flat', 'PostThread', {
|
||||
name: 'alice.test',
|
||||
rkey: '123',
|
||||
})
|
||||
|
||||
expect(state).toEqual({
|
||||
index: 0,
|
||||
routes: [{name: 'PostThread', params: {name: 'alice.test', rkey: '123'}}],
|
||||
})
|
||||
})
|
||||
})
|
||||
+2
-5
@@ -30,7 +30,7 @@ import {
|
||||
} from '#/lib/hooks/useNotificationHandler'
|
||||
import {useWebScrollRestoration} from '#/lib/hooks/useWebScrollRestoration'
|
||||
import {useCallOnce} from '#/lib/once'
|
||||
import {buildStateObject} from '#/lib/routes/helpers'
|
||||
import {buildStateObject, getCurrentRoute} from '#/lib/routes/helpers'
|
||||
import {
|
||||
type AllNavigatorParams,
|
||||
type BottomTabNavigatorParams,
|
||||
@@ -783,10 +783,7 @@ const LINKING = {
|
||||
|
||||
getPathFromState(state: State) {
|
||||
// find the current node in the navigation tree
|
||||
let node = state.routes[state.index || 0]
|
||||
while (node.state?.routes && typeof node.state?.index === 'number') {
|
||||
node = node.state?.routes[node.state?.index]
|
||||
}
|
||||
const node = getCurrentRoute(state)
|
||||
|
||||
// build the path
|
||||
const route = router.matchName(node.name)
|
||||
|
||||
@@ -13,12 +13,20 @@ export function getRootNavigation<T extends {}>(
|
||||
|
||||
export function getCurrentRoute(state?: State) {
|
||||
if (!state) {
|
||||
return {name: 'Home'}
|
||||
return {name: 'Home', params: undefined}
|
||||
}
|
||||
|
||||
/*
|
||||
* Nested navigator states may still be partial ("stale") right after a cold
|
||||
* start from a deep link, in which case `index` is not set yet. React
|
||||
* Navigation focuses the last route when it rehydrates such a state, so
|
||||
* mirror that here instead of stopping the descent early and misreporting
|
||||
* the tab root as the current route (which e.g. re-enabled the drawer
|
||||
* swipe gesture on top of a deep-linked screen).
|
||||
*/
|
||||
let node = state.routes[state.index || 0]
|
||||
while (node.state?.routes && typeof node.state?.index === 'number') {
|
||||
node = node.state?.routes[node.state?.index]
|
||||
while (node.state?.routes) {
|
||||
node = node.state.routes[node.state.index ?? node.state.routes.length - 1]
|
||||
}
|
||||
return node
|
||||
}
|
||||
@@ -83,15 +91,24 @@ export function buildStateObject(
|
||||
) {
|
||||
if (stack === 'Flat') {
|
||||
return {
|
||||
index: 0,
|
||||
routes: [{name: route, params}],
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Set `index` explicitly so consumers of this state (e.g. getCurrentRoute)
|
||||
* can tell which route is focused before React Navigation has rehydrated
|
||||
* the nested navigator state.
|
||||
*/
|
||||
const routes = [...state, {name: route, params}]
|
||||
return {
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
name: stack,
|
||||
state: {
|
||||
routes: [...state, {name: route, params}],
|
||||
index: routes.length - 1,
|
||||
routes,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user