refactor: track history for tabs and drawer in a history key

This commit is contained in:
Satyajit Sahoo
2020-01-21 12:44:13 +01:00
parent b931ae62df
commit eeae11033a
6 changed files with 469 additions and 386 deletions

View File

@@ -16,6 +16,10 @@ export type NavigationState = {
* List of valid route names as defined in the screen components.
*/
routeNames: string[];
/**
* Alternative entries for history.
*/
history?: unknown[];
/**
* List of rendered routes.
*/

View File

@@ -168,7 +168,7 @@ export default function DrawerView({
<SafeAreaProviderCompat>
<DrawerGestureContext.Provider value={drawerGestureRef}>
<Drawer
open={state.isDrawerOpen}
open={Boolean(state.history.find(it => it.type === 'drawer'))}
gestureEnabled={gestureEnabled !== false}
onOpen={handleDrawerOpen}
onClose={handleDrawerClose}

View File

@@ -1,5 +1,5 @@
import { CommonActions } from '@react-navigation/core';
import { DrawerRouter, DrawerActions } from '../src';
import { DrawerRouter, DrawerActions, DrawerNavigationState } from '../src';
jest.mock('shortid', () => () => 'test');
@@ -17,14 +17,13 @@ it('gets initial state from route names and params with initialRouteName', () =>
).toEqual({
index: 1,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'baz-test' }],
stale: false,
type: 'drawer',
});
@@ -44,14 +43,13 @@ it('gets initial state from route names and params without initialRouteName', ()
).toEqual({
index: 0,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'bar-test' }],
stale: false,
type: 'drawer',
});
@@ -81,14 +79,13 @@ it('gets rehydrated state from partial state', () => {
).toEqual({
index: 0,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-1', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'bar-0' }],
stale: false,
type: 'drawer',
});
@@ -103,14 +100,13 @@ it('gets rehydrated state from partial state', () => {
).toEqual({
index: 1,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-0', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'baz-0' }],
stale: false,
type: 'drawer',
});
@@ -130,14 +126,13 @@ it('gets rehydrated state from partial state', () => {
).toEqual({
index: 2,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-1', name: 'baz', params: { answer: 42 } },
{ key: 'qux-2', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'qux-2' }],
stale: false,
type: 'drawer',
});
@@ -153,14 +148,13 @@ it('gets rehydrated state from partial state', () => {
).toEqual({
index: 2,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'qux-test' }],
stale: false,
type: 'drawer',
});
@@ -169,8 +163,12 @@ it('gets rehydrated state from partial state', () => {
router.getRehydratedState(
{
index: 1,
isDrawerOpen: true,
routeKeyHistory: ['bar-test', 'qux-test', 'foo-test'],
history: [
{ type: 'route', key: 'bar-test' },
{ type: 'route', key: 'qux-test' },
{ type: 'route', key: 'foo-test' },
{ type: 'drawer' },
],
routes: [],
},
options
@@ -178,14 +176,17 @@ it('gets rehydrated state from partial state', () => {
).toEqual({
index: 1,
key: 'drawer-test',
isDrawerOpen: true,
routeKeyHistory: ['bar-test', 'qux-test'],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [
{ type: 'route', key: 'bar-test' },
{ type: 'route', key: 'qux-test' },
{ type: 'drawer' },
],
stale: false,
type: 'drawer',
});
@@ -194,17 +195,16 @@ it('gets rehydrated state from partial state', () => {
it("doesn't rehydrate state if it's not stale", () => {
const router = DrawerRouter({});
const state = {
const state: DrawerNavigationState = {
index: 0,
key: 'drawer-test',
isDrawerOpen: true,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'bar-test' }, { type: 'drawer' }],
stale: false as const,
type: 'drawer' as const,
};
@@ -232,12 +232,11 @@ it('handles navigate action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
isDrawerOpen: false,
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }],
},
CommonActions.navigate('baz', { answer: 42 }),
options
@@ -248,12 +247,14 @@ it('handles navigate action', () => {
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
isDrawerOpen: false,
routeKeyHistory: ['bar'],
routes: [
{ key: 'baz', name: 'baz', params: { answer: 42 } },
{ key: 'bar', name: 'bar' },
],
history: [
{ type: 'route', key: 'bar' },
{ type: 'route', key: 'baz' },
],
});
});
@@ -272,12 +273,11 @@ it('handles navigate action with open drawer', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
isDrawerOpen: true,
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }, { type: 'drawer' }],
},
CommonActions.navigate('baz', { answer: 42 }),
options
@@ -288,12 +288,14 @@ it('handles navigate action with open drawer', () => {
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
isDrawerOpen: false,
routeKeyHistory: ['bar'],
routes: [
{ key: 'baz', name: 'baz', params: { answer: 42 } },
{ key: 'bar', name: 'bar' },
],
history: [
{ type: 'route', key: 'bar' },
{ type: 'route', key: 'baz' },
],
});
});
@@ -312,12 +314,11 @@ it('handles open drawer action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
isDrawerOpen: false,
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }],
},
DrawerActions.openDrawer(),
options
@@ -328,26 +329,24 @@ it('handles open drawer action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
isDrawerOpen: true,
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }, { type: 'drawer' }],
});
const state = {
const state: DrawerNavigationState = {
stale: false as const,
type: 'drawer' as const,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
isDrawerOpen: true,
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }, { type: 'drawer' }],
};
expect(
@@ -370,12 +369,11 @@ it('handles close drawer action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
isDrawerOpen: true,
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }, { type: 'drawer' }],
},
DrawerActions.closeDrawer(),
options
@@ -386,26 +384,27 @@ it('handles close drawer action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
isDrawerOpen: false,
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }],
});
const state = {
const state: DrawerNavigationState = {
stale: false as const,
type: 'drawer' as const,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
isDrawerOpen: false,
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [
{ type: 'route', key: 'bar' },
{ type: 'route', key: 'baz' },
],
};
expect(
@@ -428,12 +427,11 @@ it('handles toggle drawer action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
isDrawerOpen: true,
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }, { type: 'drawer' }],
},
DrawerActions.toggleDrawer(),
options
@@ -444,12 +442,11 @@ it('handles toggle drawer action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
isDrawerOpen: false,
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }],
});
expect(
@@ -460,12 +457,11 @@ it('handles toggle drawer action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
isDrawerOpen: false,
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }],
},
DrawerActions.toggleDrawer(),
options
@@ -476,39 +472,38 @@ it('handles toggle drawer action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
isDrawerOpen: true,
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }, { type: 'drawer' }],
});
});
it('updates route key history on focus change', () => {
it('updates history on focus change', () => {
const router = DrawerRouter({ backBehavior: 'history' });
const state = {
const state: DrawerNavigationState = {
index: 0,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routeNames: ['baz', 'bar'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz', params: { answer: 42 } },
{ key: 'qux-0', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'bar-0' }],
stale: false as const,
type: 'drawer' as const,
};
expect(router.getStateForRouteFocus(state, 'bar-0').routeKeyHistory).toEqual(
[]
);
expect(router.getStateForRouteFocus(state, 'bar-0').history).toEqual([
{ type: 'route', key: 'bar-0' },
]);
expect(router.getStateForRouteFocus(state, 'baz-0').routeKeyHistory).toEqual([
'bar-0',
expect(router.getStateForRouteFocus(state, 'baz-0').history).toEqual([
{ type: 'route', key: 'bar-0' },
{ type: 'route', key: 'baz-0' },
]);
});
@@ -520,14 +515,13 @@ it('closes drawer on focus change', () => {
{
index: 0,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
history: [{ type: 'route', key: 'bar-0' }],
stale: false,
type: 'drawer',
},
@@ -535,15 +529,17 @@ it('closes drawer on focus change', () => {
)
).toEqual({
index: 1,
isDrawerOpen: false,
key: 'drawer-test',
routeKeyHistory: ['bar-0'],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
history: [
{ type: 'route', key: 'bar-0' },
{ type: 'route', key: 'baz-0' },
],
stale: false,
type: 'drawer',
});
@@ -553,14 +549,13 @@ it('closes drawer on focus change', () => {
{
index: 0,
key: 'drawer-test',
isDrawerOpen: true,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
history: [{ type: 'route', key: 'bar-0' }, { type: 'drawer' }],
stale: false,
type: 'drawer',
},
@@ -568,15 +563,17 @@ it('closes drawer on focus change', () => {
)
).toEqual({
index: 1,
isDrawerOpen: false,
key: 'drawer-test',
routeKeyHistory: ['bar-0'],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
history: [
{ type: 'route', key: 'bar-0' },
{ type: 'route', key: 'baz-0' },
],
stale: false,
type: 'drawer',
});

View File

@@ -17,13 +17,13 @@ it('gets initial state from route names and params with initialRouteName', () =>
).toEqual({
index: 1,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'baz-test' }],
stale: false,
type: 'tab',
});
@@ -43,13 +43,13 @@ it('gets initial state from route names and params without initialRouteName', ()
).toEqual({
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'bar-test' }],
stale: false,
type: 'tab',
});
@@ -79,13 +79,13 @@ it('gets rehydrated state from partial state', () => {
).toEqual({
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-1', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'bar-0' }],
stale: false,
type: 'tab',
});
@@ -100,13 +100,13 @@ it('gets rehydrated state from partial state', () => {
).toEqual({
index: 1,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-0', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'baz-0' }],
stale: false,
type: 'tab',
});
@@ -126,13 +126,13 @@ it('gets rehydrated state from partial state', () => {
).toEqual({
index: 2,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-1', name: 'baz', params: { answer: 42 } },
{ key: 'qux-2', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'qux-2' }],
stale: false,
type: 'tab',
});
@@ -148,13 +148,13 @@ it('gets rehydrated state from partial state', () => {
).toEqual({
index: 2,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'qux-test' }],
stale: false,
type: 'tab',
});
@@ -163,7 +163,11 @@ it('gets rehydrated state from partial state', () => {
router.getRehydratedState(
{
index: 1,
routeKeyHistory: ['bar-test', 'qux-test', 'foo-test'],
history: [
{ type: 'route', key: 'bar-test' },
{ type: 'route', key: 'qux-test' },
{ type: 'route', key: 'foo-test' },
],
routes: [],
},
options
@@ -171,13 +175,16 @@ it('gets rehydrated state from partial state', () => {
).toEqual({
index: 1,
key: 'tab-test',
routeKeyHistory: ['bar-test', 'qux-test'],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [
{ type: 'route', key: 'bar-test' },
{ type: 'route', key: 'qux-test' },
],
stale: false,
type: 'tab',
});
@@ -186,18 +193,18 @@ it('gets rehydrated state from partial state', () => {
it("doesn't rehydrate state if it's not stale", () => {
const router = TabRouter({});
const state = {
const state: TabNavigationState = {
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false as const,
type: 'tab' as const,
history: [{ type: 'route', key: 'bar-test' }],
stale: false,
type: 'tab',
};
expect(
@@ -216,13 +223,13 @@ it('gets state on route names change', () => {
{
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'bar-test' }],
stale: false,
type: 'tab',
},
@@ -237,7 +244,6 @@ it('gets state on route names change', () => {
).toEqual({
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['qux', 'baz', 'foo', 'fiz'],
routes: [
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
@@ -245,6 +251,7 @@ it('gets state on route names change', () => {
{ key: 'foo-test', name: 'foo' },
{ key: 'fiz-test', name: 'fiz', params: { fruit: 'apple' } },
],
history: [{ type: 'route', key: 'qux-test' }],
stale: false,
type: 'tab',
});
@@ -258,13 +265,13 @@ it('preserves focused route on route names change', () => {
{
index: 1,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'baz-test' }],
stale: false,
type: 'tab',
},
@@ -279,7 +286,6 @@ it('preserves focused route on route names change', () => {
).toEqual({
index: 3,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['qux', 'foo', 'fiz', 'baz'],
routes: [
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
@@ -287,6 +293,7 @@ it('preserves focused route on route names change', () => {
{ key: 'fiz-test', name: 'fiz', params: { fruit: 'apple' } },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
],
history: [{ type: 'route', key: 'baz-test' }],
stale: false,
type: 'tab',
});
@@ -300,13 +307,13 @@ it('falls back to first route if route is removed on route names change', () =>
{
index: 1,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route', key: 'baz-test' }],
stale: false,
type: 'tab',
},
@@ -321,13 +328,13 @@ it('falls back to first route if route is removed on route names change', () =>
).toEqual({
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['qux', 'foo', 'fiz'],
routes: [
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
{ key: 'foo-test', name: 'foo' },
{ key: 'fiz-test', name: 'fiz', params: { fruit: 'apple' } },
],
history: [{ type: 'route', key: 'qux-test' }],
stale: false,
type: 'tab',
});
@@ -348,11 +355,11 @@ it('handles navigate action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [
{ key: 'baz-1', name: 'baz' },
{ key: 'bar-1', name: 'bar' },
],
history: [{ type: 'route', key: 'bar-1' }],
},
CommonActions.navigate({ key: 'bar-1', params: { answer: 42 } }),
options
@@ -363,11 +370,11 @@ it('handles navigate action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: ['bar-1'],
routes: [
{ key: 'baz-1', name: 'baz' },
{ key: 'bar-1', name: 'bar', params: { answer: 42 } },
],
history: [{ type: 'route', key: 'bar-1' }],
});
expect(
@@ -378,11 +385,11 @@ it('handles navigate action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }],
},
CommonActions.navigate('baz', { answer: 42 }),
options
@@ -393,11 +400,14 @@ it('handles navigate action', () => {
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: ['bar'],
routes: [
{ key: 'baz', name: 'baz', params: { answer: 42 } },
{ key: 'bar', name: 'bar' },
],
history: [
{ type: 'route', key: 'bar' },
{ type: 'route', key: 'baz' },
],
});
expect(
@@ -408,11 +418,11 @@ it('handles navigate action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'bar' }],
},
CommonActions.navigate('non-existent'),
options
@@ -435,11 +445,11 @@ it('handles jump to action', () => {
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [{ type: 'route', key: 'baz' }],
},
TabActions.jumpTo('bar'),
options
@@ -450,213 +460,259 @@ it('handles jump to action', () => {
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: ['baz'],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
history: [
{ type: 'route', key: 'baz' },
{ type: 'route', key: 'bar' },
],
});
});
it('handles back action with backBehavior: history', () => {
const router = TabRouter({ backBehavior: 'history' });
const options = {
routeNames: ['bar', 'baz'],
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {},
};
let state = router.getInitialState(options);
expect(
router.getStateForAction(
{
stale: false,
type: 'tab',
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: ['bar'],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
},
CommonActions.goBack(),
options
)
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual(null);
state = router.getStateForAction(
state,
TabActions.jumpTo('qux'),
options
) as TabNavigationState;
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual({
stale: false,
type: 'tab',
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
key: 'tab-test',
index: 0,
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz' },
{ key: 'qux-test', name: 'qux' },
],
history: [{ type: 'route', key: 'bar-test' }],
});
state = router.getStateForAction(
state,
TabActions.jumpTo('baz'),
options
) as TabNavigationState;
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual({
stale: false,
type: 'tab',
key: 'tab-test',
index: 2,
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz' },
{ key: 'qux-test', name: 'qux' },
],
history: [
{ type: 'route', key: 'bar-test' },
{ type: 'route', key: 'qux-test' },
],
});
state = router.getStateForAction(
state,
TabActions.jumpTo('bar'),
options
) as TabNavigationState;
expect(
router.getStateForAction(
{
stale: false,
type: 'tab',
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
},
CommonActions.goBack(),
options
)
).toBe(null);
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual({
stale: false,
type: 'tab',
key: 'tab-test',
index: 1,
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz' },
{ key: 'qux-test', name: 'qux' },
],
history: [
{ type: 'route', key: 'qux-test' },
{ type: 'route', key: 'baz-test' },
],
});
});
it('handles back action with backBehavior: order', () => {
const router = TabRouter({ backBehavior: 'order' });
const options = {
routeNames: ['bar', 'baz'],
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {},
};
let state = router.getInitialState(options);
expect(
router.getStateForAction(
{
stale: false,
type: 'tab',
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
},
CommonActions.goBack(),
options
)
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual(null);
state = router.getStateForAction(
state,
TabActions.jumpTo('qux'),
options
) as TabNavigationState;
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual({
stale: false,
type: 'tab',
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
key: 'tab-test',
index: 1,
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz' },
{ key: 'qux-test', name: 'qux' },
],
history: [
{ type: 'route', key: 'bar-test' },
{ type: 'route', key: 'baz-test' },
],
});
state = router.getStateForAction(
state,
TabActions.jumpTo('baz'),
options
) as TabNavigationState;
expect(
router.getStateForAction(
{
stale: false,
type: 'tab',
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
},
CommonActions.goBack(),
options
)
).toBe(null);
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual({
stale: false,
type: 'tab',
key: 'tab-test',
index: 0,
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz' },
{ key: 'qux-test', name: 'qux' },
],
history: [{ type: 'route', key: 'bar-test' }],
});
state = router.getStateForAction(
state,
TabActions.jumpTo('bar'),
options
) as TabNavigationState;
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual(null);
});
it('handles back action with backBehavior: initialRoute', () => {
const router = TabRouter({
backBehavior: 'initialRoute',
initialRouteName: 'bar',
});
const router = TabRouter({ backBehavior: 'initialRoute' });
const options = {
routeNames: ['bar', 'baz'],
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {},
};
let state = router.getInitialState(options);
expect(
router.getStateForAction(
{
stale: false,
type: 'tab',
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
},
CommonActions.goBack(),
options
)
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual(null);
state = router.getStateForAction(
state,
TabActions.jumpTo('qux'),
options
) as TabNavigationState;
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual({
stale: false,
type: 'tab',
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
key: 'tab-test',
index: 0,
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz' },
{ key: 'qux-test', name: 'qux' },
],
history: [{ type: 'route', key: 'bar-test' }],
});
state = router.getStateForAction(
state,
TabActions.jumpTo('baz'),
options
) as TabNavigationState;
expect(
router.getStateForAction(
{
stale: false,
type: 'tab',
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
},
CommonActions.goBack(),
options
)
).toBe(null);
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual({
stale: false,
type: 'tab',
key: 'tab-test',
index: 0,
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz' },
{ key: 'qux-test', name: 'qux' },
],
history: [{ type: 'route', key: 'bar-test' }],
});
state = router.getStateForAction(
state,
TabActions.jumpTo('bar'),
options
) as TabNavigationState;
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual(null);
});
it('handles back action with backBehavior: none', () => {
const router = TabRouter({ backBehavior: 'none' });
const options = {
routeNames: ['bar', 'baz'],
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {},
};
let state = router.getInitialState(options);
state = router.getStateForAction(
state,
TabActions.jumpTo('baz'),
options
) as TabNavigationState;
expect(
router.getStateForAction(
{
stale: false,
type: 'tab',
key: 'root',
index: 0,
routeNames: ['bar', 'baz'],
routeKeyHistory: [],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
],
},
CommonActions.goBack(),
options
)
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual(null);
});
@@ -670,8 +726,8 @@ it('updates route key history on navigate and jump to', () => {
let state: TabNavigationState = {
index: 1,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
history: [{ type: 'route', key: 'baz-0' }],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz', params: { answer: 42 } },
@@ -681,15 +737,16 @@ it('updates route key history on navigate and jump to', () => {
type: 'tab',
};
expect(state.routeKeyHistory).toEqual([]);
state = router.getStateForAction(
state,
TabActions.jumpTo('qux'),
options
) as TabNavigationState;
expect(state.routeKeyHistory).toEqual(['baz-0']);
expect(state.history).toEqual([
{ type: 'route', key: 'baz-0' },
{ type: 'route', key: 'qux-0' },
]);
state = router.getStateForAction(
state,
@@ -697,7 +754,11 @@ it('updates route key history on navigate and jump to', () => {
options
) as TabNavigationState;
expect(state.routeKeyHistory).toEqual(['baz-0', 'qux-0']);
expect(state.history).toEqual([
{ type: 'route', key: 'baz-0' },
{ type: 'route', key: 'qux-0' },
{ type: 'route', key: 'bar-0' },
]);
state = router.getStateForAction(
state,
@@ -705,7 +766,11 @@ it('updates route key history on navigate and jump to', () => {
options
) as TabNavigationState;
expect(state.routeKeyHistory).toEqual(['qux-0', 'bar-0']);
expect(state.history).toEqual([
{ type: 'route', key: 'qux-0' },
{ type: 'route', key: 'bar-0' },
{ type: 'route', key: 'baz-0' },
]);
state = router.getStateForAction(
state,
@@ -713,7 +778,10 @@ it('updates route key history on navigate and jump to', () => {
options
) as TabNavigationState;
expect(state.routeKeyHistory).toEqual(['qux-0']);
expect(state.history).toEqual([
{ type: 'route', key: 'qux-0' },
{ type: 'route', key: 'bar-0' },
]);
state = router.getStateForAction(
state,
@@ -721,7 +789,7 @@ it('updates route key history on navigate and jump to', () => {
options
) as TabNavigationState;
expect(state.routeKeyHistory).toEqual([]);
expect(state.history).toEqual([{ type: 'route', key: 'qux-0' }]);
});
it('updates route key history on focus change', () => {
@@ -730,22 +798,23 @@ it('updates route key history on focus change', () => {
const state = {
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz', params: { answer: 42 } },
{ key: 'qux-0', name: 'qux', params: { name: 'Jane' } },
],
history: [{ type: 'route' as const, key: 'bar-0' }],
stale: false as const,
type: 'tab' as const,
};
expect(router.getStateForRouteFocus(state, 'bar-0').routeKeyHistory).toEqual(
[]
);
expect(router.getStateForRouteFocus(state, 'bar-0').history).toEqual([
{ type: 'route', key: 'bar-0' },
]);
expect(router.getStateForRouteFocus(state, 'baz-0').routeKeyHistory).toEqual([
'bar-0',
expect(router.getStateForRouteFocus(state, 'baz-0').history).toEqual([
{ type: 'route', key: 'bar-0' },
{ type: 'route', key: 'baz-0' },
]);
});

View File

@@ -1,5 +1,5 @@
import shortid from 'shortid';
import { CommonAction, Router } from '@react-navigation/core';
import { CommonAction, Router, PartialState } from '@react-navigation/core';
import TabRouter, {
TabActions,
TabActionType,
@@ -17,15 +17,18 @@ export type DrawerActionType =
export type DrawerRouterOptions = TabRouterOptions;
export type DrawerNavigationState = Omit<TabNavigationState, 'type'> & {
export type DrawerNavigationState = Omit<
TabNavigationState,
'type' | 'history'
> & {
/**
* Type of the router, in this case, it's drawer.
*/
type: 'drawer';
/**
* Whether the drawer is open or closed.
* List of previously visited route keys and drawer open status.
*/
isDrawerOpen: boolean;
history: ({ type: 'route'; key: string } | { type: 'drawer' })[];
};
export const DrawerActions = {
@@ -41,6 +44,32 @@ export const DrawerActions = {
},
};
const isDrawerOpen = (
state: DrawerNavigationState | PartialState<DrawerNavigationState>
) => Boolean(state.history?.find(it => it.type === 'drawer'));
const openDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
if (isDrawerOpen(state)) {
return state;
}
return {
...state,
history: [...state.history, { type: 'drawer' }],
};
};
const closeDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
if (!isDrawerOpen(state)) {
return state;
}
return {
...state,
history: state.history.filter(it => it.type !== 'drawer'),
};
};
export default function DrawerRouter(
options: DrawerRouterOptions
): Router<DrawerNavigationState, DrawerActionType | CommonAction> {
@@ -55,24 +84,13 @@ export default function DrawerRouter(
type: 'drawer',
getInitialState({ routeNames, routeParamList }) {
const index =
options.initialRouteName === undefined
? 0
: routeNames.indexOf(options.initialRouteName);
const state = router.getInitialState({ routeNames, routeParamList });
return {
...state,
stale: false,
type: 'drawer',
key: `drawer-${shortid()}`,
index,
routeNames,
routeKeyHistory: [],
routes: routeNames.map(name => ({
name,
key: `${name}-${shortid()}`,
params: routeParamList[name],
})),
isDrawerOpen: false,
};
},
@@ -81,84 +99,46 @@ export default function DrawerRouter(
return partialState;
}
const state = router.getRehydratedState(partialState, {
let state = router.getRehydratedState(partialState, {
routeNames,
routeParamList,
});
if (isDrawerOpen(partialState)) {
state = openDrawer(state);
}
return {
...state,
type: 'drawer',
key: `drawer-${shortid()}`,
isDrawerOpen:
typeof partialState.isDrawerOpen === 'boolean'
? partialState.isDrawerOpen
: false,
};
},
getStateForRouteFocus(state, key) {
const index = state.routes.findIndex(r => r.key === key);
const result = router.getStateForRouteFocus(state, key);
const result =
index === -1 || index === state.index
? state
: router.getStateForRouteFocus(state, key);
if (result.isDrawerOpen) {
return {
...result,
isDrawerOpen: false,
};
}
return result;
return closeDrawer(result);
},
getStateForAction(state, action, options) {
switch (action.type) {
case 'OPEN_DRAWER':
if (state.isDrawerOpen) {
return state;
}
return {
...state,
isDrawerOpen: true,
};
return openDrawer(state);
case 'CLOSE_DRAWER':
if (!state.isDrawerOpen) {
return state;
}
return {
...state,
isDrawerOpen: false,
};
return closeDrawer(state);
case 'TOGGLE_DRAWER':
return {
...state,
isDrawerOpen: !state.isDrawerOpen,
};
if (isDrawerOpen(state)) {
return closeDrawer(state);
}
case 'NAVIGATE':
return router.getStateForAction(
{
...state,
isDrawerOpen: false,
},
action,
options
);
return openDrawer(state);
case 'GO_BACK':
if (state.isDrawerOpen) {
return {
...state,
isDrawerOpen: false,
};
if (isDrawerOpen(state)) {
return closeDrawer(state);
}
return router.getStateForAction(state, action, options);

View File

@@ -16,11 +16,13 @@ export type TabActionType = {
target?: string;
};
export type BackBehavior = 'initialRoute' | 'order' | 'history' | 'none';
export type TabRouterOptions = DefaultRouterOptions & {
backBehavior?: 'initialRoute' | 'order' | 'history' | 'none';
backBehavior?: BackBehavior;
};
export type TabNavigationState = NavigationState & {
export type TabNavigationState = Omit<NavigationState, 'history'> & {
/**
* Type of the router, in this case, it's tab.
*/
@@ -28,26 +30,64 @@ export type TabNavigationState = NavigationState & {
/**
* List of previously visited route keys.
*/
routeKeyHistory: string[];
history: { type: 'route'; key: string }[];
};
const TYPE_ROUTE = 'route' as const;
export const TabActions = {
jumpTo(name: string, params?: object): TabActionType {
return { type: 'JUMP_TO', payload: { name, params } };
},
};
const changeIndex = (state: TabNavigationState, index: number) => {
const previousKey = state.routes[state.index].key;
const currentKey = state.routes[index].key;
const routeKeyHistory = state.routeKeyHistory
.filter(key => key !== currentKey && key !== previousKey)
.concat(previousKey);
const getRouteHistory = (
routes: Route<string>[],
index: number,
backBehavior: BackBehavior
) => {
const history = [{ type: TYPE_ROUTE, key: routes[index].key }];
switch (backBehavior) {
case 'initialRoute':
if (index !== 0) {
history.unshift({ type: TYPE_ROUTE, key: routes[0].key });
}
break;
case 'order':
for (let i = index; i > 0; i--) {
history.unshift({ type: TYPE_ROUTE, key: routes[i - 1].key });
}
break;
case 'history':
// The history will fill up on navigation
break;
}
return history;
};
const changeIndex = (
state: TabNavigationState,
index: number,
backBehavior: BackBehavior
) => {
let history;
if (backBehavior === 'history') {
const currentKey = state.routes[index].key;
history = state.history
.filter(it => (it.type === 'route' ? it.key !== currentKey : false))
.concat({ type: TYPE_ROUTE, key: currentKey });
} else {
history = getRouteHistory(state.routes, index, backBehavior);
}
return {
...state,
index,
routeKeyHistory,
history,
};
};
@@ -66,18 +106,22 @@ export default function TabRouter({
? routeNames.indexOf(initialRouteName)
: 0;
const routes = routeNames.map(name => ({
name,
key: `${name}-${shortid()}`,
params: routeParamList[name],
}));
const history = getRouteHistory(routes, index, backBehavior);
return {
stale: false,
type: 'tab',
key: `tab-${shortid()}`,
index,
routeNames,
routeKeyHistory: [],
routes: routeNames.map(name => ({
name,
key: `${name}-${shortid()}`,
params: routeParamList[name],
})),
history,
routes,
};
},
@@ -122,9 +166,13 @@ export default function TabRouter({
routes.length - 1
);
const routeKeyHistory = state.routeKeyHistory
? state.routeKeyHistory.filter(key => routes.find(r => r.key === key))
: [];
let history = state.history?.filter(it =>
routes.find(r => r.key === it.key)
);
if (!history?.length) {
history = getRouteHistory(routes, index, backBehavior);
}
return {
stale: false,
@@ -132,7 +180,7 @@ export default function TabRouter({
key: `tab-${shortid()}`,
index,
routeNames,
routeKeyHistory,
history,
routes,
};
},
@@ -152,8 +200,17 @@ export default function TabRouter({
routeNames.indexOf(state.routes[state.index].name)
);
let history = state.history.filter(it =>
routes.find(r => r.key === it.key)
);
if (!history.length) {
history = getRouteHistory(routes, index, backBehavior);
}
return {
...state,
history,
routeNames,
routes,
index,
@@ -167,7 +224,7 @@ export default function TabRouter({
return state;
}
return changeIndex(state, index);
return changeIndex(state, index, backBehavior);
},
getStateForAction(state, action) {
@@ -208,56 +265,32 @@ export default function TabRouter({
)
: state.routes,
},
index
index,
backBehavior
);
}
case 'GO_BACK':
switch (backBehavior) {
case 'initialRoute': {
const index = initialRouteName
? state.routeNames.indexOf(initialRouteName)
: 0;
if (index === -1 || index === state.index) {
return null;
}
return { ...state, index };
}
case 'order':
if (state.index === 0) {
return null;
}
return {
...state,
index: state.index - 1,
};
case 'history': {
const previousKey =
state.routeKeyHistory[state.routeKeyHistory.length - 1];
const index = state.routes.findIndex(
route => route.key === previousKey
);
if (index === -1) {
return null;
}
return {
...state,
routeKeyHistory: state.routeKeyHistory.slice(0, -1),
index,
};
}
default:
return null;
case 'GO_BACK': {
if (state.history.length === 1) {
return null;
}
const previousKey = state.history[state.history.length - 2].key;
const index = state.routes.findIndex(
route => route.key === previousKey
);
if (index === -1) {
return null;
}
return {
...state,
history: state.history.slice(0, -1),
index,
};
}
default:
return BaseRouter.getStateForAction(state, action);
}