Compare commits

..

5 Commits

Author SHA1 Message Date
Eric Vicenti
0ea01673e5 transitioner2
Introducing a wild and hacky prototype of Transitioner 2!

- "transitionProps" concept is now identical to transitioner.state
- Far simpler state management than before
- Descriptors only, no "scenes"
- No more "position"

Also, StackView is seeing improvements:
- Easier to understand when "transition props" are just this.props

- StackView only renders the current sceen and the one before it

Currently broken:
- Interpolation configuration, beyond the first push
- Attempting to move away from getSceneIndicesForInterpolationInputRange because it works with position and it is confusing as hell, but haven't worked around it yet
- Gestures, although some "backProgress" code is in the works
- Interpolation is super buggy
- onTransitionStart/End
- Header, although a lot of code is moved over
2018-03-11 00:11:58 -08:00
Brent Vatne
71adb7cc4f Update DrawerView.js 2018-03-06 14:22:31 -08:00
Eric Vicenti
77343cb096 Merge branch 'master' into @ericvicenti/drawer-fix 2018-03-06 11:43:25 -08:00
Brent Vatne
accee76951 Merge branch 'master' into @ericvicenti/drawer-fix 2018-03-05 16:23:10 -08:00
Eric Vicenti
bbb8c4d8d3 Fix issue in drawer actions 2018-03-05 11:38:13 -08:00
143 changed files with 11232 additions and 13887 deletions

View File

@@ -7,18 +7,18 @@
"prettier/react"
],
"parser": "babel-eslint",
"plugins": ["react", "prettier"],
"plugins": [
"react",
"prettier"
],
"env": {
"jasmine": true
},
"rules": {
"prettier/prettier": [
"error",
{
"trailingComma": "es5",
"singleQuote": true
}
],
"prettier/prettier": ["error", {
"trailingComma": "es5",
"singleQuote": true
}],
"no-underscore-dangle": "off",
"no-use-before-define": "off",
@@ -27,20 +27,24 @@
"no-plusplus": "off",
"no-class-assign": "off",
"no-duplicate-imports": "off",
"import/extensions": "off",
"import/no-extraneous-dependencies": "off",
"import/no-unresolved": "off",
"react/jsx-filename-extension": ["off", { "extensions": [".js", ".jsx"] }],
"react/jsx-filename-extension": [
"off", { "extensions": [".js", ".jsx"] }
],
"react/sort-comp": "off",
"react/prefer-stateless-function": "off",
"react/no-deprecated": "off",
"react/forbid-prop-types": "warn",
"react/prop-types": "off",
"react/require-default-props": "off",
"react/no-unused-prop-types": "off"
"react/no-unused-prop-types": "off",
},
"settings": {
},
"parserOptions": {
"ecmaVersion": 6,

View File

@@ -1,21 +1,17 @@
Please provide enough information so that others can review your pull request:
## Motivation
Explain the **motivation** for making this change. What existing problem does the pull request solve?
## Test plan
Prefer **small pull requests**. These are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise split it.
Demonstrate the code is solid. Example: the exact commands you ran and their output, screenshots / videos if the pull request changes UI.
**Test plan (required)**
Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI.
Make sure you test on both platforms if your change affects both platforms.
The code must pass tests.
## Code formatting
**Code formatting**
Look around. Match the style of the rest of the codebase. Run `yarn format` before committing.
## Changelog
Add an entry under the "Unreleased" heading in [CHANGELOG.md](https://github.com/react-navigation/react-navigation/blob/master/CHANGELOG.md#unreleased) which explains your change.
Look around. Match the style of the rest of the codebase.

View File

@@ -1,29 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [2.5.1] - [2018-06-22](https://github.com/react-navigation/react-navigation/releases/tag/2.5.1)
### Fixed
- `transitionConfig` in stack navigator no longer passes incorrect `fromTransitionProps` when navigating back
## [2.5.0] - [2018-06-22](https://github.com/react-navigation/react-navigation/releases/tag/2.5.0)
### Changed
- Refactor internals to make it play more nicely with web
### Fixed
- `const defaultGetStateForAction = SwitchBasedNavigator.router.getStateForAction` no longer throws error.
- Updated react-navigation-drawer to 0.4.1 which should fix issues related to automatically closing drawer when changing routes.
## [2.4.1] - [2018-06-21](https://github.com/react-navigation/react-navigation/releases/tag/2.4.1)
### Changed
- Improved examples
[Unreleased]: https://github.com/react-navigation/react-navigation/compare/2.5.1...HEAD
[2.5.0]: https://github.com/react-navigation/react-navigation/compare/2.5.0...2.5.1
[2.5.0]: https://github.com/react-navigation/react-navigation/compare/2.4.1...2.5.0
[2.4.1]: https://github.com/react-navigation/react-navigation/compare/2.4.0...2.4.1

View File

@@ -1,6 +1,6 @@
# React Navigation
[![npm version](https://badge.fury.io/js/react-navigation.svg)](https://badge.fury.io/js/react-navigation) [![codecov](https://codecov.io/gh/react-navigation/react-navigation/branch/master/graph/badge.svg)](https://codecov.io/gh/react-navigation/react-navigation) [![CircleCI badge](https://circleci.com/gh/react-navigation/react-navigation/tree/master.svg?style=shield)](https://circleci.com/gh/react-navigation/react-navigation/tree/master) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://reactnavigation.org/docs/contributing.html)
[![npm version](https://badge.fury.io/js/react-navigation.svg)](https://badge.fury.io/js/react-navigation) [![codecov](https://codecov.io/gh/react-community/react-navigation/branch/master/graph/badge.svg)](https://codecov.io/gh/react-community/react-navigation) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://reactnavigation.org/docs/guides/contributors)
React Navigation is born from the React Native community's need for an extensible yet easy-to-use navigation solution based on Javascript.
@@ -55,4 +55,4 @@ This library has adopted a Code of Conduct that we expect project participants t
## License
React Navigation is licensed under the [BSD 2-clause "Simplified" License](https://github.com/react-community/react-navigation/blob/master/LICENSE).
React-navigation is licensed under the [BSD 2-clause "Simplified" License](https://github.com/react-community/react-navigation/blob/master/LICENSE).

View File

@@ -1,12 +0,0 @@
/**
* This file is needed to hijack asset imports so that test files don't attempt
* to import them as JavaScript modules.
* See https://github.com/facebook/jest/issues/2663#issuecomment-317109798
*/
const path = require('path');
module.exports = {
process(src, filename, config, options) {
return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
},
};

View File

@@ -55,6 +55,8 @@ module.system=haste
emoji=true
experimental.strict_type_args=true
munge_underscores=true
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
@@ -75,5 +77,7 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
unsafe.enable_getters_and_setters=true
[version]
^0.67.0
^0.61.0

View File

@@ -5,6 +5,5 @@ import renderer from 'react-test-renderer';
it('renders without crashing', () => {
const rendered = renderer.create(<App />).toJSON();
// Will be null because the playground uses state persistence which happens asyncronously
expect(rendered).toEqual(null);
expect(rendered).toBeTruthy();
});

View File

@@ -4,6 +4,6 @@ A playground for experimenting with react-navigation in a pure-JS React Native a
## Usage
Please see the [Contributors Guide](https://reactnavigation.org/docs/contributing.html#run-the-example-app) for instructions on running these example apps.
Please see the [Contributors Guide](https://reactnavigation.org/docs/guides/contributors#Run-the-Example-App) for instructions on running these example apps.
You can view this example application directly on your phone by visiting [our expo demo](https://exp.host/@react-navigation/NavigationPlayground).

View File

@@ -11,17 +11,16 @@
"splash": {
"image": "./assets/icons/splash.png"
},
"sdkVersion": "27.0.0",
"sdkVersion": "25.0.0",
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"assetBundlePatterns": [
"**/*"
],
"ios": {
"bundleIdentifier": "com.reactnavigation.example",
"supportsTablet": true
"packagerOpts": {
"assetExts": [
"ttf",
"mp4"
]
},
"android": {
"package": "com.reactnavigation.example"
"ios": {
"supportsTablet": true
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ import {
StatusBar,
View,
} from 'react-native';
import { SafeAreaView, createStackNavigator } from 'react-navigation';
import { SafeAreaView, StackNavigator } from 'react-navigation';
import CustomTabs from './CustomTabs';
import CustomTransitioner from './CustomTransitioner';
@@ -26,36 +26,19 @@ import TabsInDrawer from './TabsInDrawer';
import ModalStack from './ModalStack';
import StacksInTabs from './StacksInTabs';
import StacksOverTabs from './StacksOverTabs';
import StacksOverTopTabs from './StacksOverTopTabs';
import StacksWithKeys from './StacksWithKeys';
import InactiveStack from './InactiveStack';
import StackWithCustomHeaderBackImage from './StackWithCustomHeaderBackImage';
import SimpleStack from './SimpleStack';
import StackWithHeaderPreset from './StackWithHeaderPreset';
import StackWithTranslucentHeader from './StackWithTranslucentHeader';
import SimpleTabs from './SimpleTabs';
import SwitchWithStacks from './SwitchWithStacks';
import TabAnimations from './TabAnimations';
import TabsWithNavigationFocus from './TabsWithNavigationFocus';
import KeyboardHandlingExample from './KeyboardHandlingExample';
const ExampleInfo = {
SimpleStack: {
name: 'Stack Example',
description: 'A card stack',
},
SwitchWithStacks: {
name: 'Switch between routes',
description: 'Jump between routes',
},
InactiveStack: {
name: 'Navigate idempotently to stacks in inactive routes',
description:
'An inactive route in a stack should be given the opportunity to handle actions',
},
StackWithCustomHeaderBackImage: {
name: 'Custom header back image',
description: 'Stack with custom header back image',
},
SimpleTabs: {
name: 'Tabs Example',
description: 'Tabs following platform conventions',
@@ -68,6 +51,10 @@ const ExampleInfo = {
name: 'UIKit-style Header Transitions',
description: 'Masked back button and sliding header items. iOS only.',
},
StackWithHeaderPreset: {
name: 'UIKit-style Header Transitions',
description: 'Masked back button and sliding header items. iOS only.',
},
StackWithTranslucentHeader: {
name: 'Translucent Header',
description: 'Render arbitrary translucent content in header background.',
@@ -106,10 +93,6 @@ const ExampleInfo = {
name: 'Stacks over Tabs',
description: 'Nested stack navigation that pushes on top of tabs',
},
StacksOverTopTabs: {
name: 'Stacks with non-standard header height',
description: 'Tab navigator in stack with custom header heights',
},
StacksWithKeys: {
name: 'Link in Stack with keys',
description: 'Use keys to link between screens',
@@ -122,32 +105,24 @@ const ExampleInfo = {
name: 'Link to Settings Tab',
description: 'Deep linking into a route in tab',
},
TabAnimations: {
name: 'Animated Tabs Example',
description: 'Tab transitions have custom animations',
},
TabsWithNavigationFocus: {
name: 'withNavigationFocus',
description: 'Receive the focus prop to know when a screen is focused',
},
KeyboardHandlingExample: {
name: 'Keyboard Handling Example',
description:
'Demo automatic handling of keyboard showing/hiding inside StackNavigator',
},
};
const ExampleRoutes = {
SimpleStack,
SwitchWithStacks,
SimpleStack: SimpleStack,
SimpleTabs: SimpleTabs,
Drawer: Drawer,
// MultipleDrawer: {
// screen: MultipleDrawer,
// },
StackWithCustomHeaderBackImage: StackWithCustomHeaderBackImage,
...Platform.select({
ios: {
StackWithHeaderPreset: StackWithHeaderPreset,
},
android: {},
}),
StackWithHeaderPreset: StackWithHeaderPreset,
StackWithTranslucentHeader: StackWithTranslucentHeader,
TabsInDrawer: TabsInDrawer,
CustomTabs: CustomTabs,
@@ -156,7 +131,6 @@ const ExampleRoutes = {
StacksWithKeys: StacksWithKeys,
StacksInTabs: StacksInTabs,
StacksOverTabs: StacksOverTabs,
StacksOverTopTabs: StacksOverTopTabs,
LinkStack: {
screen: SimpleStack,
path: 'people/Jordan',
@@ -165,10 +139,8 @@ const ExampleRoutes = {
screen: SimpleTabs,
path: 'settings',
},
TabAnimations,
TabsWithNavigationFocus,
KeyboardHandlingExample,
// This is commented out because it's rarely useful
// InactiveStack,
};
type State = {
@@ -179,7 +151,7 @@ class MainScreen extends React.Component<any, State> {
scrollY: new Animated.Value(0),
};
componentDidMount() {
componentWillMount() {
Asset.fromModule(
require('react-navigation/src/views/assets/back-icon-mask.png')
).downloadAsync();
@@ -314,7 +286,7 @@ class MainScreen extends React.Component<any, State> {
}
}
const AppNavigator = createStackNavigator(
const AppNavigator = StackNavigator(
{
...ExampleRoutes,
Index: {
@@ -333,7 +305,8 @@ const AppNavigator = createStackNavigator(
}
);
export default AppNavigator;
// export default () => <AppNavigator />;
export default SimpleStack;
const styles = StyleSheet.create({
item: {

View File

@@ -4,6 +4,7 @@
import React from 'react';
import {
Button,
Platform,
ScrollView,
StyleSheet,
@@ -19,7 +20,6 @@ import {
TabRouter,
} from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>

View File

@@ -1,6 +1,7 @@
import React, { Component, PropTypes } from 'react';
import {
Animated,
Button,
Easing,
Image,
Platform,
@@ -16,7 +17,6 @@ import {
createNavigator,
} from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<SafeAreaView forceInset={{ top: 'always' }}>

View File

@@ -3,15 +3,14 @@
*/
import React from 'react';
import { Platform, ScrollView, StatusBar } from 'react-native';
import { Button, Platform, ScrollView, StatusBar } from 'react-native';
import {
createStackNavigator,
createDrawerNavigator,
StackNavigator,
DrawerNavigator,
SafeAreaView,
} from 'react-navigation';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
@@ -32,26 +31,6 @@ const InboxScreen = ({ navigation }) => (
<MyNavScreen banner={'Inbox Screen'} navigation={navigation} />
);
InboxScreen.navigationOptions = {
headerTitle: 'Inbox',
};
const EmailScreen = ({ navigation }) => (
<MyNavScreen banner={'Email Screen'} navigation={navigation} />
);
const DraftsScreen = ({ navigation }) => (
<MyNavScreen banner={'Drafts Screen'} navigation={navigation} />
);
DraftsScreen.navigationOptions = {
headerTitle: 'Drafts',
};
const InboxStack = createStackNavigator({
Inbox: { screen: InboxScreen },
Email: { screen: EmailScreen },
});
InboxStack.navigationOptions = {
drawerLabel: 'Inbox',
drawerIcon: ({ tintColor }) => (
<MaterialIcons
@@ -62,19 +41,31 @@ InboxStack.navigationOptions = {
),
};
const DraftsStack = createStackNavigator({
Drafts: { screen: DraftsScreen },
Email: { screen: EmailScreen },
});
const EmailScreen = ({ navigation }) => (
<MyNavScreen banner={'Email Screen'} navigation={navigation} />
);
DraftsStack.navigationOptions = {
const DraftsScreen = ({ navigation }) => (
<MyNavScreen banner={'Drafts Screen'} navigation={navigation} />
);
DraftsScreen.navigationOptions = {
drawerLabel: 'Drafts',
drawerIcon: ({ tintColor }) => (
<MaterialIcons name="drafts" size={24} style={{ color: tintColor }} />
),
};
const DrawerExample = createDrawerNavigator(
const InboxStack = StackNavigator({
Inbox: { screen: InboxScreen },
Email: { screen: EmailScreen },
});
const DraftsStack = StackNavigator({
Drafts: { screen: DraftsScreen },
Email: { screen: EmailScreen },
});
const DrawerExample = DrawerNavigator(
{
Inbox: {
path: '/',

View File

@@ -1,96 +0,0 @@
import React from 'react';
import { Button, Text, StatusBar, View, StyleSheet } from 'react-native';
import {
SafeAreaView,
createStackNavigator,
createSwitchNavigator,
NavigationActions,
} from 'react-navigation';
const runSubRoutes = navigation => {
navigation.dispatch(NavigationActions.navigate({ routeName: 'First2' }));
navigation.dispatch(NavigationActions.navigate({ routeName: 'Second2' }));
navigation.dispatch(NavigationActions.navigate({ routeName: 'First2' }));
};
const runSubRoutesWithIntermediate = navigation => {
navigation.dispatch(toFirst1);
navigation.dispatch(toSecond2);
navigation.dispatch(toFirst);
navigation.dispatch(toFirst2);
};
const runSubAction = navigation => {
navigation.dispatch(toFirst2);
navigation.dispatch(toSecond2);
navigation.dispatch(toFirstChild1);
};
const DummyScreen = ({ routeName, navigation, style }) => {
return (
<SafeAreaView
style={[
StyleSheet.absoluteFill,
{
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'white',
},
style,
]}
>
<Text style={{ fontWeight: '800' }}>
{routeName}({navigation.state.key})
</Text>
<View>
<Button title="back" onPress={() => navigation.goBack()} />
<Button title="dismiss" onPress={() => navigation.dismiss()} />
<Button
title="between sub-routes"
onPress={() => runSubRoutes(navigation)}
/>
<Button
title="between sub-routes (with intermediate)"
onPress={() => runSubRoutesWithIntermediate(navigation)}
/>
<Button
title="with sub-action"
onPress={() => runSubAction(navigation)}
/>
</View>
<StatusBar barStyle="default" />
</SafeAreaView>
);
};
const createDummyScreen = routeName => {
const BoundDummyScreen = props => DummyScreen({ ...props, routeName });
return BoundDummyScreen;
};
const toFirst = NavigationActions.navigate({ routeName: 'First' });
const toFirst1 = NavigationActions.navigate({ routeName: 'First1' });
const toFirst2 = NavigationActions.navigate({ routeName: 'First2' });
const toSecond2 = NavigationActions.navigate({ routeName: 'Second2' });
const toFirstChild1 = NavigationActions.navigate({
routeName: 'First',
action: NavigationActions.navigate({ routeName: 'First1' }),
});
export default createStackNavigator(
{
Other: createDummyScreen('Leaf'),
First: createStackNavigator({
First1: createDummyScreen('First1'),
First2: createDummyScreen('First2'),
}),
Second: createStackNavigator({
Second1: createDummyScreen('Second1'),
Second2: createDummyScreen('Second2'),
}),
},
{
headerMode: 'none',
}
);

View File

@@ -1,63 +0,0 @@
import React from 'react';
import { StatusBar, View, TextInput, InteractionManager } from 'react-native';
import { createStackNavigator, withNavigationFocus } from 'react-navigation';
import { Button } from './commonComponents/ButtonWithMargin';
class ScreenOne extends React.Component {
static navigationOptions = {
title: 'Home',
};
render() {
const { navigation } = this.props;
return (
<View style={{ paddingTop: 30 }}>
<Button
onPress={() => navigation.push('ScreenTwo')}
title="Push screen with focused text input"
/>
<Button onPress={() => navigation.goBack(null)} title="Go Home" />
<StatusBar barStyle="default" />
</View>
);
}
}
class ScreenTwo extends React.Component {
static navigationOptions = ({ navigation }) => ({
title: navigation.getParam('inputValue', 'Screen w/ Input'),
});
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
this._textInput.focus();
});
}
render() {
const { navigation } = this.props;
return (
<View style={{ paddingTop: 30 }}>
<View style={{ alignSelf: 'center', paddingVertical: 20 }}>
<TextInput
ref={c => (this._textInput = c)}
onChangeText={inputValue => navigation.setParams({ inputValue })}
style={{
backgroundColor: 'white',
height: 24,
width: 150,
borderColor: '#555',
borderWidth: 1,
}}
/>
</View>
<Button onPress={() => navigation.pop()} title="Pop" />
</View>
);
}
}
export default createStackNavigator({
ScreenOne,
ScreenTwo: withNavigationFocus(ScreenTwo),
});

View File

@@ -3,10 +3,9 @@
*/
import React from 'react';
import { ScrollView, StatusBar, Text } from 'react-native';
import { SafeAreaView, createStackNavigator } from 'react-navigation';
import { Button, ScrollView, StatusBar, Text } from 'react-native';
import { SafeAreaView, StackNavigator } from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
@@ -32,8 +31,7 @@ const MyNavScreen = ({ navigation, banner }) => (
headerVisible:
!navigation.state.params ||
!navigation.state.params.headerVisible,
})
}
})}
/>
)}
<Button onPress={() => navigation.goBack(null)} title="Go back" />
@@ -59,7 +57,7 @@ MyProfileScreen.navigationOptions = ({ navigation }) => ({
title: `${navigation.state.params.name}'s Profile!`,
});
const ProfileNavigator = createStackNavigator(
const ProfileNavigator = StackNavigator(
{
Home: {
screen: MyHomeScreen,
@@ -89,7 +87,7 @@ MyHeaderTestScreen.navigationOptions = ({ navigation }) => {
};
};
const ModalStack = createStackNavigator(
const ModalStack = StackNavigator(
{
ProfileNavigator: {
screen: ProfileNavigator,

View File

@@ -3,11 +3,10 @@
*/
import React from 'react';
import { Platform, ScrollView, StyleSheet } from 'react-native';
import { createDrawerNavigator } from 'react-navigation';
import { Button, Platform, ScrollView, StyleSheet } from 'react-native';
import { DrawerNavigator } from 'react-navigation';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView style={styles.container}>
@@ -41,7 +40,7 @@ DraftsScreen.navigationOptions = {
),
};
const DrawerExample = createDrawerNavigator(
const DrawerExample = DrawerNavigator(
{
Inbox: {
path: '/',
@@ -60,7 +59,7 @@ const DrawerExample = createDrawerNavigator(
}
);
const MainDrawerExample = createDrawerNavigator({
const MainDrawerExample = DrawerNavigator({
Drafts: {
screen: DrawerExample,
},

View File

@@ -4,42 +4,22 @@
import type {
NavigationScreenProp,
NavigationState,
NavigationStateRoute,
NavigationEventSubscription,
} from 'react-navigation';
import * as React from 'react';
import { ScrollView, StatusBar } from 'react-native';
import {
createStackNavigator,
SafeAreaView,
withNavigation,
NavigationActions,
StackActions,
} from 'react-navigation';
import invariant from 'invariant';
import { Button, ScrollView, StatusBar } from 'react-native';
import { StackNavigator, SafeAreaView, withNavigation } from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
import { HeaderButtons } from './commonComponents/HeaderButtons';
type MyNavScreenProps = {
navigation: NavigationScreenProp<NavigationState>,
navigation: NavigationScreenProp<*>,
banner: React.Node,
};
type BackButtonProps = {
navigation: NavigationScreenProp<NavigationStateRoute>,
};
class MyBackButton extends React.Component<BackButtonProps, any> {
class MyBackButton extends React.Component<any, any> {
render() {
return (
<HeaderButtons>
<HeaderButtons.Item title="Back" onPress={this._navigateBack} />
</HeaderButtons>
);
return <Button onPress={this._navigateBack} title="Custom Back" />;
}
_navigateBack = () => {
@@ -52,55 +32,24 @@ const MyBackButtonWithNavigation = withNavigation(MyBackButton);
class MyNavScreen extends React.Component<MyNavScreenProps> {
render() {
const { navigation, banner } = this.props;
const { push, replace, popToTop, pop, dismiss } = navigation;
invariant(
push && replace && popToTop && pop && dismiss,
'missing action creators for StackNavigator'
);
return (
<SafeAreaView>
<SampleText>{banner}</SampleText>
<Button
onPress={() => push('Profile', { name: 'Jane' })}
onPress={() => navigation.push('Profile', { name: 'Jane' })}
title="Push a profile screen"
/>
<Button
onPress={() =>
navigation.dispatch(
StackActions.reset({
index: 0,
actions: [
NavigationActions.navigate({
routeName: 'Photos',
params: { name: 'Jane' },
}),
],
})
)
}
title="Reset photos"
/>
<Button
onPress={() => navigation.navigate('Photos', { name: 'Jane' })}
title="Navigate to a photos screen"
/>
<Button
onPress={() => replace('Profile', { name: 'Lucy' })}
onPress={() => navigation.replace('Profile', { name: 'Lucy' })}
title="Replace with profile"
/>
<Button onPress={() => popToTop()} title="Pop to top" />
<Button onPress={() => pop()} title="Pop" />
<Button
onPress={() => {
if (navigation.goBack()) {
console.log('goBack handled');
} else {
console.log('goBack unhandled');
}
}}
title="Go back"
/>
<Button onPress={() => dismiss()} title="Dismiss" />
<Button onPress={() => navigation.popToTop()} title="Pop to top" />
<Button onPress={() => navigation.pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
@@ -108,7 +57,7 @@ class MyNavScreen extends React.Component<MyNavScreenProps> {
}
type MyHomeScreenProps = {
navigation: NavigationScreenProp<NavigationState>,
navigation: NavigationScreenProp<*>,
};
class MyHomeScreen extends React.Component<MyHomeScreenProps> {
@@ -152,7 +101,7 @@ class MyHomeScreen extends React.Component<MyHomeScreenProps> {
}
type MyPhotosScreenProps = {
navigation: NavigationScreenProp<NavigationState>,
navigation: NavigationScreenProp<*>,
};
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
static navigationOptions = {
@@ -193,7 +142,7 @@ class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
const { navigation } = this.props;
return (
<MyNavScreen
banner={`${navigation.getParam('name')}'s Photos`}
banner={`${navigation.state.params.name}'s Photos`}
navigation={navigation}
/>
);
@@ -202,9 +151,9 @@ class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
const MyProfileScreen = ({ navigation }) => (
<MyNavScreen
banner={`${
navigation.getParam('mode') === 'edit' ? 'Now Editing ' : ''
}${navigation.getParam('name')}'s Profile`}
banner={`${navigation.state.params.mode === 'edit' ? 'Now Editing ' : ''}${
navigation.state.params.name
}'s Profile`}
navigation={navigation}
/>
);
@@ -219,19 +168,17 @@ MyProfileScreen.navigationOptions = props => {
// Render a button on the right side of the header.
// When pressed switches the screen to edit mode.
headerRight: (
<HeaderButtons>
<HeaderButtons.Item
title={params.mode === 'edit' ? 'Done' : 'Edit'}
onPress={() =>
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
}
/>
</HeaderButtons>
<Button
title={params.mode === 'edit' ? 'Done' : 'Edit'}
onPress={() =>
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
}
/>
),
};
};
const SimpleStack = createStackNavigator({
const SimpleStack = StackNavigator({
Home: {
screen: MyHomeScreen,
},

View File

@@ -8,11 +8,10 @@ import type {
} from 'react-navigation';
import React from 'react';
import { Platform, ScrollView, StatusBar, View } from 'react-native';
import { SafeAreaView, createBottomTabNavigator } from 'react-navigation';
import { Button, Platform, ScrollView, StatusBar, View } from 'react-native';
import { SafeAreaView, TabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
@@ -144,7 +143,7 @@ MySettingsScreen.navigationOptions = {
),
};
const SimpleTabs = createBottomTabNavigator(
const SimpleTabs = TabNavigator(
{
Home: {
screen: MyHomeScreen,
@@ -164,8 +163,10 @@ const SimpleTabs = createBottomTabNavigator(
},
},
{
lazy: true,
removeClippedSubviews: true,
tabBarOptions: {
activeTintColor: '#e91e63',
activeTintColor: Platform.OS === 'ios' ? '#e91e63' : '#fff',
},
}
);

View File

@@ -1,145 +0,0 @@
/**
* @flow
*/
import type { NavigationScreenProp } from 'react-navigation';
import * as React from 'react';
import { Image, Button, StatusBar, StyleSheet } from 'react-native';
import { createStackNavigator, SafeAreaView } from 'react-navigation';
import SampleText from './SampleText';
type MyNavScreenProps = {
navigation: NavigationScreenProp<*>,
banner: React.Node,
};
class MyCustomHeaderBackImage extends React.Component<any, any> {
render() {
const source = require('./assets/back.png');
return (
<Image
source={source}
style={[styles.myCustomHeaderBackImage, this.props.style]}
/>
);
}
}
class MyNavScreen extends React.Component<MyNavScreenProps> {
render() {
const { navigation, banner } = this.props;
return (
<SafeAreaView>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('Photos', { name: 'Jane' })}
title="Navigate to a photos screen"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
type MyHomeScreenProps = {
navigation: NavigationScreenProp<*>,
};
class MyHomeScreen extends React.Component<MyHomeScreenProps> {
static navigationOptions = {
title: 'Welcome',
headerBackTitle: null,
};
render() {
const { navigation } = this.props;
return <MyNavScreen banner="Home Screen" navigation={navigation} />;
}
}
type MyPhotosScreenProps = {
navigation: NavigationScreenProp<*>,
};
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
static navigationOptions = ({ navigation }) => ({
title: `${navigation.state.params.name}'s photos`,
headerBackTitle: null,
});
render() {
const { navigation } = this.props;
return (
<SafeAreaView>
<SampleText>{`${navigation.state.params.name}'s Photos`}</SampleText>
<Button
onPress={() => navigation.navigate('Profile', { name: 'Jane' })}
title="Navigate to a profile screen"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
type MyProfileScreenProps = {
navigation: NavigationScreenProp<*>,
};
class MyProfileScreen extends React.Component<MyProfileScreenProps> {
static navigationOptions = ({ navigation }) => ({
title: 'Profile',
headerBackImage: (
<MyCustomHeaderBackImage style={styles.myCustomHeaderBackImageAlt} />
),
});
render() {
const { navigation } = this.props;
return (
<SafeAreaView>
<SampleText>{`${navigation.state.params.name}'s Profile`}</SampleText>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
const StackWithCustomHeaderBackImage = createStackNavigator(
{
Home: {
screen: MyHomeScreen,
},
Photos: {
path: 'photos/:name',
screen: MyPhotosScreen,
},
Profile: {
path: 'profile/:name',
screen: MyProfileScreen,
},
},
{
navigationOptions: {
headerBackImage: MyCustomHeaderBackImage,
},
}
);
export default StackWithCustomHeaderBackImage;
const styles = StyleSheet.create({
myCustomHeaderBackImage: {
height: 14.5,
width: 24,
marginLeft: 9,
marginRight: 12,
marginVertical: 12,
resizeMode: 'contain',
},
myCustomHeaderBackImageAlt: {
tintColor: '#f00',
},
});

View File

@@ -4,11 +4,8 @@
import type { NavigationScreenProp } from 'react-navigation';
import * as React from 'react';
import { ScrollView, StatusBar } from 'react-native';
import { createStackNavigator, SafeAreaView } from 'react-navigation';
import invariant from 'invariant';
import { Button } from './commonComponents/ButtonWithMargin';
import { Button, ScrollView, StatusBar } from 'react-native';
import { StackNavigator, SafeAreaView } from 'react-navigation';
type NavScreenProps = {
navigation: NavigationScreenProp<*>,
@@ -21,17 +18,15 @@ class HomeScreen extends React.Component<NavScreenProps> {
render() {
const { navigation } = this.props;
const { push } = navigation;
invariant(push, 'missing `push` action creator for StackNavigator');
return (
<SafeAreaView style={{ paddingTop: 30 }}>
<Button onPress={() => push('Other')} title="Push another screen" />
<Button
onPress={() => push('ScreenWithNoHeader')}
title="Push screen with no header"
onPress={() => navigation.push('Other')}
title="Push another screen"
/>
<Button onPress={() => navigation.goBack(null)} title="Go Home" />
<Button onPress={() => navigation.pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
@@ -45,20 +40,14 @@ class OtherScreen extends React.Component<NavScreenProps> {
render() {
const { navigation } = this.props;
const { push, pop } = navigation;
invariant(push && pop, 'missing action creators for StackNavigator');
return (
<SafeAreaView style={{ paddingTop: 30 }}>
<Button
onPress={() => push('ScreenWithLongTitle')}
onPress={() => navigation.push('Other')}
title="Push another screen"
/>
<Button
onPress={() => push('ScreenWithNoHeader')}
title="Push screen with no header"
/>
<Button onPress={() => pop()} title="Pop" />
<Button onPress={() => navigation.pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
@@ -66,54 +55,10 @@ class OtherScreen extends React.Component<NavScreenProps> {
}
}
class ScreenWithLongTitle extends React.Component<NavScreenProps> {
static navigationOptions = {
title: "Another title that's kind of long",
};
render() {
const { navigation } = this.props;
const { pop } = navigation;
invariant(pop, 'missing `pop` action creator for StackNavigator');
return (
<SafeAreaView style={{ paddingTop: 30 }}>
<Button onPress={() => pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
class ScreenWithNoHeader extends React.Component<NavScreenProps> {
static navigationOptions = {
header: null,
title: 'No Header',
};
render() {
const { navigation } = this.props;
const { push, pop } = navigation;
invariant(push && pop, 'missing action creators for StackNavigator');
return (
<SafeAreaView style={{ paddingTop: 30 }}>
<Button onPress={() => push('Other')} title="Push another screen" />
<Button onPress={() => pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
const StackWithHeaderPreset = createStackNavigator(
const StackWithHeaderPreset = StackNavigator(
{
Home: HomeScreen,
Other: OtherScreen,
ScreenWithNoHeader: ScreenWithNoHeader,
ScreenWithLongTitle: ScreenWithLongTitle,
},
{
headerTransitionPreset: 'uikit',

View File

@@ -12,18 +12,15 @@ import { isIphoneX } from 'react-native-iphone-x-helper';
import * as React from 'react';
import { BlurView, Constants } from 'expo';
import {
Button,
Dimensions,
Platform,
ScrollView,
StatusBar,
View,
} from 'react-native';
import { Header, createStackNavigator } from 'react-navigation';
import invariant from 'invariant';
import { Header, StackNavigator } from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
import { HeaderButtons } from './commonComponents/HeaderButtons';
type MyNavScreenProps = {
navigation: NavigationScreenProp<*>,
@@ -33,16 +30,11 @@ type MyNavScreenProps = {
class MyNavScreen extends React.Component<MyNavScreenProps> {
render() {
const { navigation, banner } = this.props;
const { push, replace, popToTop, pop } = navigation;
invariant(
push && replace && popToTop && pop,
'missing action creators for StackNavigator'
);
return (
<ScrollView style={{ flex: 1 }} {...this.getHeaderInset()}>
<SampleText>{banner}</SampleText>
<Button
onPress={() => push('Profile', { name: 'Jane' })}
onPress={() => navigation.push('Profile', { name: 'Jane' })}
title="Push a profile screen"
/>
<Button
@@ -50,11 +42,11 @@ class MyNavScreen extends React.Component<MyNavScreenProps> {
title="Navigate to a photos screen"
/>
<Button
onPress={() => replace('Profile', { name: 'Lucy' })}
onPress={() => navigation.replace('Profile', { name: 'Lucy' })}
title="Replace with profile"
/>
<Button onPress={() => popToTop()} title="Pop to top" />
<Button onPress={() => pop()} title="Pop" />
<Button onPress={() => navigation.popToTop()} title="Pop to top" />
<Button onPress={() => navigation.pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</ScrollView>
@@ -201,19 +193,17 @@ MyProfileScreen.navigationOptions = props => {
// Render a button on the right side of the header.
// When pressed switches the screen to edit mode.
headerRight: (
<HeaderButtons>
<HeaderButtons.Item
title={params.mode === 'edit' ? 'Done' : 'Edit'}
onPress={() =>
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
}
/>
</HeaderButtons>
<Button
title={params.mode === 'edit' ? 'Done' : 'Edit'}
onPress={() =>
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
}
/>
),
};
};
const StackWithTranslucentHeader = createStackNavigator(
const StackWithTranslucentHeader = StackNavigator(
{
Home: {
screen: MyHomeScreen,

View File

@@ -3,16 +3,11 @@
*/
import React from 'react';
import { ScrollView, StatusBar } from 'react-native';
import {
SafeAreaView,
createStackNavigator,
createBottomTabNavigator,
} from 'react-navigation';
import { Button, ScrollView, StatusBar } from 'react-native';
import { SafeAreaView, StackNavigator, TabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
@@ -56,7 +51,7 @@ const MySettingsScreen = ({ navigation }) => (
<MyNavScreen banner="Settings Screen" navigation={navigation} />
);
const MainTab = createStackNavigator({
const MainTab = StackNavigator({
Home: {
screen: MyHomeScreen,
path: '/',
@@ -73,7 +68,7 @@ const MainTab = createStackNavigator({
},
});
const SettingsTab = createStackNavigator({
const SettingsTab = StackNavigator({
Settings: {
screen: MySettingsScreen,
path: '/',
@@ -89,7 +84,7 @@ const SettingsTab = createStackNavigator({
},
});
const StacksInTabs = createBottomTabNavigator(
const StacksInTabs = TabNavigator(
{
MainTab: {
screen: MainTab,
@@ -121,9 +116,9 @@ const StacksInTabs = createBottomTabNavigator(
},
},
{
tabBarOptions: {
showLabel: false,
},
tabBarPosition: 'bottom',
animationEnabled: false,
swipeEnabled: false,
}
);

View File

@@ -3,16 +3,11 @@
*/
import React from 'react';
import { ScrollView, StatusBar } from 'react-native';
import {
SafeAreaView,
createStackNavigator,
createBottomTabNavigator,
} from 'react-navigation';
import { Button, ScrollView, StatusBar } from 'react-native';
import { SafeAreaView, StackNavigator, TabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
@@ -55,7 +50,7 @@ const MySettingsScreen = ({ navigation }) => (
<MyNavScreen banner="Settings Screen" navigation={navigation} />
);
const TabNav = createBottomTabNavigator(
const TabNav = TabNavigator(
{
MainTab: {
screen: MyHomeScreen,
@@ -94,20 +89,7 @@ const TabNav = createBottomTabNavigator(
}
);
TabNav.navigationOptions = ({ navigation }) => {
let { routeName } = navigation.state.routes[navigation.state.index];
let title;
if (routeName === 'SettingsTab') {
title = 'Settings';
} else if (routeName === 'MainTab') {
title = 'Home';
}
return {
title,
};
};
const StacksOverTabs = createStackNavigator({
const StacksOverTabs = StackNavigator({
Root: {
screen: TabNav,
},
@@ -120,9 +102,9 @@ const StacksOverTabs = createStackNavigator({
Profile: {
screen: MyProfileScreen,
path: '/people/:name',
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.name}'s Profile!`,
}),
navigationOptions: ({ navigation }) => {
title: `${navigation.state.params.name}'s Profile!`;
},
},
});

View File

@@ -1,142 +0,0 @@
/**
* @flow
*/
import React from 'react';
import { View, ScrollView, StatusBar, StyleSheet } from 'react-native';
import {
SafeAreaView,
createStackNavigator,
createMaterialTopTabNavigator,
} from 'react-navigation';
import { Constants } from 'expo';
import { MaterialTopTabBar } from 'react-navigation-tabs';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const HEADER_HEIGHT = 64;
const MyNavScreen = ({ navigation, banner, statusBarStyle }) => (
<ScrollView>
<SafeAreaView forceInset={{ horizontal: 'always' }}>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
title="Open profile screen"
/>
<Button
onPress={() => navigation.navigate('NotifSettings')}
title="Open notifications screen"
/>
<Button
onPress={() => navigation.navigate('SettingsTab')}
title="Go to settings tab"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
</SafeAreaView>
<StatusBar barStyle={statusBarStyle || 'default'} />
</ScrollView>
);
const MyHomeScreen = ({ navigation }) => (
<MyNavScreen
banner="Home Screen"
navigation={navigation}
statusBarStyle="light-content"
/>
);
const MyProfileScreen = ({ navigation }) => (
<MyNavScreen
banner={`${navigation.state.params.name}s Profile`}
navigation={navigation}
/>
);
const MyNotificationsSettingsScreen = ({ navigation }) => (
<MyNavScreen banner="Notifications Screen" navigation={navigation} />
);
const MySettingsScreen = ({ navigation }) => (
<MyNavScreen
banner="Settings Screen"
navigation={navigation}
statusBarStyle="light-content"
/>
);
const styles = StyleSheet.create({
stackHeader: {
height: HEADER_HEIGHT,
},
tab: {
height: HEADER_HEIGHT,
},
});
function MaterialTopTabBarWithStatusBar(props) {
return (
<View
style={{
paddingTop: Constants.statusBarHeight,
backgroundColor: '#2196f3',
}}
>
<MaterialTopTabBar {...props} jumpToIndex={() => {}} />
</View>
);
}
const TabNavigator = createMaterialTopTabNavigator(
{
MainTab: {
screen: MyHomeScreen,
navigationOptions: {
title: 'Welcome',
},
},
SettingsTab: {
screen: MySettingsScreen,
navigationOptions: {
title: 'Settings',
},
},
},
{
tabBarComponent: MaterialTopTabBarWithStatusBar,
tabBarOptions: {
tabStyle: styles.tab,
},
}
);
const StackNavigator = createStackNavigator(
{
Root: {
screen: TabNavigator,
navigationOptions: {
header: null,
},
},
NotifSettings: {
screen: MyNotificationsSettingsScreen,
navigationOptions: {
title: 'Notifications',
},
},
Profile: {
screen: MyProfileScreen,
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.name}'s Profile!`,
}),
},
},
{
navigationOptions: {
headerStyle: styles.stackHeader,
},
}
);
export default StackNavigator;

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { StatusBar, Text, View } from 'react-native';
import { createStackNavigator } from 'react-navigation';
import { Button } from './commonComponents/ButtonWithMargin';
import { Button, StatusBar, Text, View } from 'react-native';
import { StackNavigator } from 'react-navigation';
class HomeScreen extends React.Component<any, any> {
render() {
@@ -82,7 +81,7 @@ class SettingsScreen extends React.Component<any, any> {
}
}
const Stack = createStackNavigator(
const Stack = StackNavigator(
{
Home: {
screen: HomeScreen,

View File

@@ -1,121 +0,0 @@
/**
* @flow
*/
import React from 'react';
import {
ActivityIndicator,
AsyncStorage,
StatusBar,
StyleSheet,
View,
} from 'react-native';
import { createStackNavigator, createSwitchNavigator } from 'react-navigation';
import { Button } from './commonComponents/ButtonWithMargin';
class SignInScreen extends React.Component<any, any> {
static navigationOptions = {
title: 'Please sign in',
};
render() {
return (
<View style={styles.container}>
<Button title="Sign in!" onPress={this._signInAsync} />
<Button
title="Go back to other examples"
onPress={() => this.props.navigation.goBack(null)}
/>
<StatusBar barStyle="default" />
</View>
);
}
_signInAsync = async () => {
await AsyncStorage.setItem('userToken', 'abc');
this.props.navigation.navigate('Home');
};
}
class HomeScreen extends React.Component<any, any> {
static navigationOptions = {
title: 'Welcome to the app!',
};
render() {
return (
<View style={styles.container}>
<Button title="Show me more of the app" onPress={this._showMoreApp} />
<Button title="Actually, sign me out :)" onPress={this._signOutAsync} />
<StatusBar barStyle="default" />
</View>
);
}
_showMoreApp = () => {
this.props.navigation.navigate('Other');
};
_signOutAsync = async () => {
await AsyncStorage.clear();
this.props.navigation.navigate('Auth');
};
}
class OtherScreen extends React.Component<any, any> {
static navigationOptions = {
title: 'Lots of features here',
};
render() {
return (
<View style={styles.container}>
<Button title="I'm done, sign me out" onPress={this._signOutAsync} />
<StatusBar barStyle="default" />
</View>
);
}
_signOutAsync = async () => {
await AsyncStorage.clear();
this.props.navigation.navigate('Auth');
};
}
class LoadingScreen extends React.Component<any, any> {
componentDidMount() {
this._bootstrapAsync();
}
_bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem('userToken');
let initialRouteName = userToken ? 'App' : 'Auth';
this.props.navigation.navigate(initialRouteName);
};
render() {
return (
<View style={styles.container}>
<ActivityIndicator />
<StatusBar barStyle="default" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
const AppStack = createStackNavigator({ Home: HomeScreen, Other: OtherScreen });
const AuthStack = createStackNavigator({ SignIn: SignInScreen });
export default createSwitchNavigator({
Loading: LoadingScreen,
App: AppStack,
Auth: AuthStack,
});

View File

@@ -0,0 +1,127 @@
/**
* @flow
*/
import React from 'react';
import { Animated, Button, ScrollView, StatusBar } from 'react-native';
import { StackNavigator, TabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import SampleText from './SampleText';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
title="Open profile screen"
/>
<Button
onPress={() => navigation.navigate('NotifSettings')}
title="Open notifications screen"
/>
<Button
onPress={() => navigation.navigate('SettingsTab')}
title="Go to settings tab"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</ScrollView>
);
const MyHomeScreen = ({ navigation }) => (
<MyNavScreen banner="Home Screen" navigation={navigation} />
);
const MyProfileScreen = ({ navigation }) => (
<MyNavScreen
banner={`${navigation.state.params.name}s Profile`}
navigation={navigation}
/>
);
const MyNotificationsSettingsScreen = ({ navigation }) => (
<MyNavScreen banner="Notifications Screen" navigation={navigation} />
);
const MySettingsScreen = ({ navigation }) => (
<MyNavScreen banner="Settings Screen" navigation={navigation} />
);
const MainTab = StackNavigator({
Home: {
screen: MyHomeScreen,
path: '/',
navigationOptions: {
title: 'Welcome',
},
},
Profile: {
screen: MyProfileScreen,
path: '/people/:name',
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.name}'s Profile!`,
}),
},
});
const SettingsTab = StackNavigator({
Settings: {
screen: MySettingsScreen,
path: '/',
navigationOptions: () => ({
title: 'Settings',
}),
},
NotifSettings: {
screen: MyNotificationsSettingsScreen,
navigationOptions: {
title: 'Notifications',
},
},
});
const TabAnimations = TabNavigator(
{
MainTab: {
screen: MainTab,
path: '/',
navigationOptions: {
tabBarLabel: 'Home',
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-home' : 'ios-home-outline'}
size={26}
style={{ color: tintColor }}
/>
),
},
},
SettingsTab: {
screen: SettingsTab,
path: '/settings',
navigationOptions: {
tabBarLabel: 'Settings',
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-settings' : 'ios-settings-outline'}
size={26}
style={{ color: tintColor }}
/>
),
},
},
},
{
tabBarPosition: 'bottom',
animationEnabled: true,
configureTransition: (currentTransitionProps,nextTransitionProps) => ({
timing: Animated.spring,
tension: 1,
friction: 35,
}),
swipeEnabled: false,
}
);
export default TabAnimations;

View File

@@ -3,13 +3,13 @@
*/
import React from 'react';
import { Platform, ScrollView } from 'react-native';
import { createDrawerNavigator } from 'react-navigation';
import { Button, Platform, ScrollView } from 'react-native';
import { TabNavigator, DrawerNavigator } from 'react-navigation';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import SimpleTabs from './SimpleTabs';
import StacksOverTabs from './StacksOverTabs';
const TabsInDrawer = createDrawerNavigator({
const TabsInDrawer = DrawerNavigator({
SimpleTabs: {
screen: SimpleTabs,
navigationOptions: {

View File

@@ -3,11 +3,9 @@
*/
import React from 'react';
import { SafeAreaView, StatusBar, Text, View } from 'react-native';
import { withNavigationFocus } from 'react-navigation';
import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs';
import { Button, SafeAreaView, Text } from 'react-native';
import { TabNavigator, withNavigationFocus } from 'react-navigation';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { Button } from './commonComponents/ButtonWithMargin';
import SampleText from './SampleText';
@@ -67,10 +65,9 @@ const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
/>
)}
<Button
onPress={() => this.props.navigation.pop()}
onPress={() => this.props.navigation.goBack(null)}
title="Back to other examples"
/>
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
@@ -78,7 +75,7 @@ const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
return withNavigationFocus(TabScreen);
};
const TabsWithNavigationFocus = createMaterialBottomTabNavigator(
const TabsWithNavigationFocus = TabNavigator(
{
One: {
screen: createTabScreen('One', 'numeric-1-box-outline', 'numeric-1-box'),
@@ -95,8 +92,9 @@ const TabsWithNavigationFocus = createMaterialBottomTabNavigator(
},
},
{
shifting: false,
activeTintColor: '#F44336',
tabBarPosition: 'bottom',
animationEnabled: true,
swipeEnabled: true,
}
);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,18 +0,0 @@
import { Button as RNButton, StyleSheet, View, Platform } from 'react-native';
import React from 'react';
export const Button = props => (
<View style={styles.margin}>
<RNButton {...props} />
</View>
);
const styles = StyleSheet.create({
margin: {
...Platform.select({
android: {
margin: 10,
},
}),
},
});

View File

@@ -1,16 +0,0 @@
import DefaultHeaderButtons from 'react-navigation-header-buttons';
import * as React from 'react';
import { Platform } from 'react-native';
export class HeaderButtons extends React.PureComponent {
static Item = DefaultHeaderButtons.Item;
render() {
return (
<DefaultHeaderButtons
color={Platform.OS === 'ios' ? '#037aff' : 'black'}
{...this.props}
/>
);
}
}

View File

@@ -8,27 +8,23 @@
"eject": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"test": "flow"
"test": "node node_modules/jest/bin/jest.js && flow"
},
"dependencies": {
"expo": "^27.0.0",
"invariant": "^2.2.4",
"react": "16.3.1",
"react-native": "^0.55.0",
"expo": "^25.0.0",
"react": "16.2.0",
"react-native": "^0.52.0",
"react-native-iphone-x-helper": "^1.0.2",
"react-navigation": "link:../..",
"react-navigation-header-buttons": "^0.0.4",
"react-navigation-material-bottom-tabs": "0.1.3",
"react-navigation-tabs": "^0.5.1"
"react-navigation": "link:../.."
},
"devDependencies": {
"babel-jest": "^22.4.1",
"babel-jest": "^21.0.0",
"babel-plugin-transform-remove-console": "^6.9.0",
"flow-bin": "^0.67.0",
"jest": "^22.1.3",
"jest-expo": "^26.0.0",
"flow-bin": "^0.61.0",
"jest": "^21.0.1",
"jest-expo": "^25.1.0",
"react-native-scripts": "^1.5.0",
"react-test-renderer": "16.3.0-alpha.1"
"react-test-renderer": "16.0.0"
},
"jest": {
"preset": "jest-expo",

View File

@@ -11,9 +11,7 @@ module.exports = {
return blacklist([
/react\-navigation\/examples\/(?!NavigationPlayground).*/,
/react\-navigation\/node_modules\/react-native\/(.*)/,
/react\-navigation\/node_modules\/react\/(.*)/,
/react\-navigation\/node_modules\/react-native-paper\/(.*)/,
/react\-navigation\/node_modules\/@expo\/vector-icons\/(.*)/,
/react\-navigation\/node_modules\/react\/(.*)/
]);
},
extraNodeModules: getNodeModulesForDirectory(path.resolve('.')),

File diff suppressed because it is too large Load Diff

View File

@@ -4,15 +4,19 @@ import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import AppReducer from './src/reducers';
import { AppNavigator, middleware } from './src/navigators/AppNavigator';
import AppWithNavigationState from './src/navigators/AppNavigator';
import { middleware } from './src/utils/redux';
const store = createStore(AppReducer, applyMiddleware(middleware));
const store = createStore(
AppReducer,
applyMiddleware(middleware),
);
class ReduxExampleApp extends React.Component {
render() {
return (
<Provider store={store}>
<AppNavigator />
<AppWithNavigationState />
</Provider>
);
}

View File

@@ -2,4 +2,4 @@
## Usage
Please see the [Contributors Guide](https://reactnavigation.org/docs/contributing.html#run-the-example-app) for instructions on running these example apps.
Please see the [Contributors Guide](https://reactnavigation.org/docs/guides/contributors#Run-the-Example-App) for instructions on running these example apps.

View File

@@ -12,7 +12,7 @@
"icon": "./assets/icons/react-navigation.png",
"hideExponentText": false
},
"sdkVersion": "27.0.0",
"sdkVersion": "25.0.0",
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"packagerOpts": {
"assetExts": ["ttf", "mp4"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -21,19 +21,18 @@
]
},
"dependencies": {
"expo": "^27.0.0",
"expo": "^25.0.0",
"prop-types": "^15.5.10",
"react": "16.3.1",
"react-native": "^0.55.0",
"react": "16.2.0",
"react-native": "^0.52.0",
"react-navigation": "link:../..",
"react-navigation-redux-helpers": "^2.0.0-beta.1",
"react-navigation-redux-helpers": "^1.0.0",
"react-redux": "^5.0.6",
"redux": "^3.7.2"
},
"devDependencies": {
"babel-jest": "^22.4.1",
"flow-bin": "^0.74.0",
"jest": "^22.1.3",
"babel-jest": "^21.0.0",
"jest": "^21.0.1",
"jest-expo": "^25.1.0",
"react-native-scripts": "^1.3.1",
"react-test-renderer": "16.0.0"

View File

@@ -1,33 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createStackNavigator } from 'react-navigation';
import {
reduxifyNavigator,
createReactNavigationReduxMiddleware,
} from 'react-navigation-redux-helpers';
import { addNavigationHelpers, StackNavigator } from 'react-navigation';
import LoginScreen from '../components/LoginScreen';
import MainScreen from '../components/MainScreen';
import ProfileScreen from '../components/ProfileScreen';
import { addListener } from '../utils/redux';
const middleware = createReactNavigationReduxMiddleware(
'root',
state => state.nav
);
const RootNavigator = createStackNavigator({
export const AppNavigator = StackNavigator({
Login: { screen: LoginScreen },
Main: { screen: MainScreen },
Profile: { screen: ProfileScreen },
});
const AppWithNavigationState = reduxifyNavigator(RootNavigator, 'root');
class AppWithNavigationState extends React.Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
nav: PropTypes.object.isRequired,
};
render() {
const { dispatch, nav } = this.props;
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch,
state: nav,
addListener,
})}
/>
);
}
}
const mapStateToProps = state => ({
state: state.nav,
nav: state.nav,
});
const AppNavigator = connect(mapStateToProps)(AppWithNavigationState);
export { RootNavigator, AppNavigator, middleware };
export default connect(mapStateToProps)(AppWithNavigationState);

View File

@@ -1,13 +1,13 @@
import { combineReducers } from 'redux';
import { NavigationActions } from 'react-navigation';
import { RootNavigator } from '../navigators/AppNavigator';
import { AppNavigator } from '../navigators/AppNavigator';
// Start with two routes: The Main screen, with the Login screen on top.
const firstAction = RootNavigator.router.getActionForPathAndParams('Main');
const tempNavState = RootNavigator.router.getStateForAction(firstAction);
const secondAction = RootNavigator.router.getActionForPathAndParams('Login');
const initialNavState = RootNavigator.router.getStateForAction(
const firstAction = AppNavigator.router.getActionForPathAndParams('Main');
const tempNavState = AppNavigator.router.getStateForAction(firstAction);
const secondAction = AppNavigator.router.getActionForPathAndParams('Login');
const initialNavState = AppNavigator.router.getStateForAction(
secondAction,
tempNavState
);
@@ -16,19 +16,19 @@ function nav(state = initialNavState, action) {
let nextState;
switch (action.type) {
case 'Login':
nextState = RootNavigator.router.getStateForAction(
nextState = AppNavigator.router.getStateForAction(
NavigationActions.back(),
state
);
break;
case 'Logout':
nextState = RootNavigator.router.getStateForAction(
nextState = AppNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'Login' }),
state
);
break;
default:
nextState = RootNavigator.router.getStateForAction(action, state);
nextState = AppNavigator.router.getStateForAction(action, state);
break;
}

View File

@@ -0,0 +1,15 @@
import {
createReactNavigationReduxMiddleware,
createReduxBoundAddListener,
} from 'react-navigation-redux-helpers';
const middleware = createReactNavigationReduxMiddleware(
"root",
state => state.nav,
);
const addListener = createReduxBoundAddListener("root");
export {
middleware,
addListener,
};

File diff suppressed because it is too large Load Diff

View File

@@ -69,14 +69,6 @@ declare module 'react-navigation' {
[key: string]: mixed,
};
declare export type NavigationBackAction = {|
type: 'Navigation/BACK',
key?: ?string,
|};
declare export type NavigationInitAction = {|
type: 'Navigation/INIT',
params?: NavigationParams,
|};
declare export type NavigationNavigateAction = {|
type: 'Navigation/NAVIGATE',
routeName: string,
@@ -87,6 +79,12 @@ declare module 'react-navigation' {
key?: string,
|};
declare export type NavigationBackAction = {|
type: 'Navigation/BACK',
key?: ?string,
|};
declare export type NavigationSetParamsAction = {|
type: 'Navigation/SET_PARAMS',
@@ -97,6 +95,30 @@ declare module 'react-navigation' {
params: NavigationParams,
|};
declare export type NavigationInitAction = {|
type: 'Navigation/INIT',
params?: NavigationParams,
|};
declare export type NavigationResetAction = {|
type: 'Navigation/RESET',
index: number,
key?: ?string,
actions: Array<NavigationNavigateAction>,
|};
declare export type NavigationUriAction = {|
type: 'Navigation/URI',
uri: string,
|};
declare export type NavigationReplaceAction = {|
+type: 'Navigation/REPLACE',
+key: string,
+routeName: string,
+params?: NavigationParams,
+action?: NavigationNavigateAction,
|};
declare export type NavigationPopAction = {|
+type: 'Navigation/POP',
+n?: number,
@@ -113,51 +135,17 @@ declare module 'react-navigation' {
+action?: NavigationNavigateAction,
+key?: string,
|};
declare export type NavigationResetAction = {|
type: 'Navigation/RESET',
index: number,
key?: ?string,
actions: Array<NavigationNavigateAction>,
|};
declare export type NavigationReplaceAction = {|
+type: 'Navigation/REPLACE',
+key: string,
+routeName: string,
+params?: NavigationParams,
+action?: NavigationNavigateAction,
|};
declare export type NavigationCompleteTransitionAction = {|
+type: 'Navigation/COMPLETE_TRANSITION',
+key?: string,
|};
declare export type NavigationOpenDrawerAction = {|
+type: 'Navigation/OPEN_DRAWER',
+key?: string,
|};
declare export type NavigationCloseDrawerAction = {|
+type: 'Navigation/CLOSE_DRAWER',
+key?: string,
|};
declare export type NavigationToggleDrawerAction = {|
+type: 'Navigation/TOGGLE_DRAWER',
+key?: string,
|};
declare export type NavigationAction =
| NavigationBackAction
| NavigationInitAction
| NavigationNavigateAction
| NavigationSetParamsAction
| NavigationReplaceAction
| NavigationPopAction
| NavigationPopToTopAction
| NavigationPushAction
| NavigationResetAction
| NavigationReplaceAction
| NavigationCompleteTransitionAction
| NavigationOpenDrawerAction
| NavigationCloseDrawerAction
| NavigationToggleDrawerAction;
| NavigationBackAction
| NavigationSetParamsAction
| NavigationResetAction;
/**
* NavigationState is a tree of routes for a single navigator, where each
@@ -281,36 +269,24 @@ declare module 'react-navigation' {
declare export type NavigationComponent =
| NavigationScreenComponent<NavigationRoute, *, *>
| NavigationContainer<*, *, *>;
declare interface withOptionalNavigationOptions<Options> {
navigationOptions?: NavigationScreenConfig<Options>;
}
| NavigationContainer<*, *, *>
| any;
declare export type NavigationScreenComponent<
Route: NavigationRoute,
Options: {},
Props: {}
> = React$ComponentType<{
...Props,
...NavigationNavigatorProps<Options, Route>,
}> &
withOptionalNavigationOptions<Options>;
declare interface withRouter<State, Options> {
router: NavigationRouter<State, Options>;
}
> = React$ComponentType<NavigationNavigatorProps<Options, Route> & Props> &
({} | { navigationOptions: NavigationScreenConfig<Options> });
declare export type NavigationNavigator<
State: NavigationState,
Options: {},
Props: {}
> = React$ComponentType<{
...Props,
...NavigationNavigatorProps<Options, State>,
}> &
withRouter<State, Options> &
withOptionalNavigationOptions<Options>;
> = React$ComponentType<NavigationNavigatorProps<Options, State> & Props> & {
router: NavigationRouter<State, Options>,
navigationOptions?: ?NavigationScreenConfig<Options>,
};
declare export type NavigationRouteConfig =
| NavigationComponent
@@ -320,6 +296,7 @@ declare module 'react-navigation' {
} & NavigationScreenRouteConfig);
declare export type NavigationScreenRouteConfig =
| NavigationComponent
| {
screen: NavigationComponent,
}
@@ -367,7 +344,7 @@ declare module 'react-navigation' {
headerTintColor?: string,
headerLeft?: React$Node | React$ElementType,
headerBackTitle?: string,
headerBackImage?: React$Node | React$ElementType,
headerBackImage?: ImageSource,
headerTruncatedBackTitle?: string,
headerBackTitleStyle?: TextStyleProp,
headerPressColorAndroid?: string,
@@ -403,20 +380,6 @@ declare module 'react-navigation' {
...NavigationStackRouterConfig,
|};
/**
* Switch Navigator
*/
declare export type NavigationSwitchRouterConfig = {|
initialRouteName?: string,
initialRouteParams?: NavigationParams,
paths?: NavigationPathsConfig,
navigationOptions?: NavigationScreenConfig<*>,
order?: Array<string>,
backBehavior?: 'none' | 'initialRoute', // defaults to `'none'`
resetOnBlur?: boolean, // defaults to `true`
|};
/**
* Tab Navigator
*/
@@ -428,6 +391,7 @@ declare module 'react-navigation' {
navigationOptions?: NavigationScreenConfig<*>,
// todo: type these as the real route names rather than 'string'
order?: Array<string>,
// Does the back button cause the router to switch to the initial tab
backBehavior?: 'none' | 'initialRoute', // defaults `initialRoute`
|};
@@ -450,10 +414,10 @@ declare module 'react-navigation' {
| ((options: { tintColor: ?string, focused: boolean }) => ?React$Node),
tabBarVisible?: boolean,
tabBarTestIDProps?: { testID?: string, accessibilityLabel?: string },
tabBarOnPress?: ({
navigation: NavigationScreenProp<NavigationRoute>,
defaultHandler: () => void,
}) => void,
tabBarOnPress?: (
scene: TabScene,
jumpToIndex: (index: number) => void
) => void,
|};
/**
@@ -509,46 +473,29 @@ declare module 'react-navigation' {
declare export type NavigationScreenProp<+S> = {
+state: S,
dispatch: NavigationDispatch,
addListener: (
eventName: string,
callback: NavigationEventCallback
) => NavigationEventSubscription,
getParam: (paramName: string, fallback?: any) => any,
isFocused: () => boolean,
// Shared action creators that exist for all routers
goBack: (routeKey?: ?string) => boolean,
navigate: (
routeName:
| string
| {
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction,
key?: string,
},
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction
) => boolean,
setParams: (newParams: NavigationParams) => boolean,
// StackRouter action creators
pop?: (n?: number, params?: { immediate?: boolean }) => boolean,
popToTop?: (params?: { immediate?: boolean }) => boolean,
push?: (
addListener: (
eventName: string,
callback: NavigationEventCallback
) => NavigationEventSubscription,
push: (
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction
) => boolean,
replace?: (
replace: (
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction
) => boolean,
reset?: (actions: NavigationAction[], index: number) => boolean,
dismiss?: () => boolean,
// DrawerRouter action creators
openDrawer?: () => boolean,
closeDrawer?: () => boolean,
toggleDrawer?: () => boolean,
pop: (n?: number, params?: { immediate?: boolean }) => boolean,
popToTop: (params?: { immediate?: boolean }) => boolean,
};
declare export type NavigationNavigatorProps<O: {}, S: {}> = $Shape<{
@@ -565,23 +512,19 @@ declare module 'react-navigation' {
State: NavigationState,
Options: {},
Props: {}
> = React$ComponentType<{
...Props,
...NavigationContainerProps<State, Options>,
}> &
withRouter<State, Options> &
withOptionalNavigationOptions<Options>;
> = React$ComponentType<NavigationContainerProps<State, Options> & Props> & {
router: NavigationRouter<State, Options>,
navigationOptions?: ?NavigationScreenConfig<Options>,
};
declare export type NavigationContainerProps<S: {}, O: {}> = $Shape<{
uriPrefix?: string | RegExp,
onNavigationStateChange?: ?(
onNavigationStateChange?: (
NavigationState,
NavigationState,
NavigationAction
) => void,
navigation?: NavigationScreenProp<S>,
persistenceKey?: ?string,
renderLoadingExperimental?: React$ComponentType<{}>,
screenProps?: *,
navigationOptions?: O,
}>;
@@ -739,73 +682,44 @@ declare module 'react-navigation' {
BACK: 'Navigation/BACK',
INIT: 'Navigation/INIT',
NAVIGATE: 'Navigation/NAVIGATE',
SET_PARAMS: 'Navigation/SET_PARAMS',
back: (payload?: { key?: ?string }) => NavigationBackAction,
init: (payload?: { params?: NavigationParams }) => NavigationInitAction,
navigate: (payload: {
routeName: string,
params?: ?NavigationParams,
action?: ?NavigationNavigateAction,
key?: string,
}) => NavigationNavigateAction,
setParams: (payload: {
key: string,
params: NavigationParams,
}) => NavigationSetParamsAction,
};
declare export var StackActions: {
POP: 'Navigation/POP',
POP_TO_TOP: 'Navigation/POP_TO_TOP',
PUSH: 'Navigation/PUSH',
RESET: 'Navigation/RESET',
REPLACE: 'Navigation/REPLACE',
COMPLETE_TRANSITION: 'Navigation/COMPLETE_TRANSITION',
pop: (payload: {
n?: number,
immediate?: boolean,
}) => NavigationPopAction,
popToTop: (payload: {
immediate?: boolean,
}) => NavigationPopToTopAction,
push: (payload: {
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction,
key?: string,
}) => NavigationPushAction,
reset: (payload: {
index: number,
key?: ?string,
actions: Array<NavigationNavigateAction>,
}) => NavigationResetAction,
replace: (payload: {
key: string,
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction,
}) => NavigationReplaceAction,
completeTransition: (payload: {
key?: string,
}) => NavigationCompleteTransitionAction,
};
declare export var DrawerActions: {
OPEN_DRAWER: 'Navigation/OPEN_DRAWER',
CLOSE_DRAWER: 'Navigation/CLOSE_DRAWER',
TOGGLE_DRAWER: 'Navigation/TOGGLE_DRAWER',
openDrawer: (payload: {
key?: string,
}) => NavigationOpenDrawerAction,
closeDrawer: (payload: {
key?: string,
}) => NavigationCloseDrawerAction,
toggleDrawer: (payload: {
key?: string,
}) => NavigationToggleDrawerAction,
SET_PARAMS: 'Navigation/SET_PARAMS',
URI: 'Navigation/URI',
back: {
(payload?: { key?: ?string }): NavigationBackAction,
toString: () => string,
},
init: {
(payload?: { params?: NavigationParams }): NavigationInitAction,
toString: () => string,
},
navigate: {
(payload: {
routeName: string,
params?: ?NavigationParams,
action?: ?NavigationNavigateAction,
}): NavigationNavigateAction,
toString: () => string,
},
reset: {
(payload: {
index: number,
key?: ?string,
actions: Array<NavigationNavigateAction>,
}): NavigationResetAction,
toString: () => string,
},
setParams: {
(payload: {
key: string,
params: NavigationParams,
}): NavigationSetParamsAction,
toString: () => string,
},
uri: {
(payload: { uri: string }): NavigationUriAction,
toString: () => string,
},
};
declare type _RouterProp<S: NavigationState, O: {}> = {
@@ -828,16 +742,12 @@ declare module 'react-navigation' {
view: NavigationView<O, S>,
router: NavigationRouter<S, O>,
navigatorConfig?: NavigatorConfig
): NavigationNavigator<S, O, *>;
): any;
declare export function StackNavigator(
routeConfigMap: NavigationRouteConfigMap,
stackConfig?: StackNavigatorConfig
): NavigationContainer<*, *, *>;
declare export function createStackNavigator(
routeConfigMap: NavigationRouteConfigMap,
stackConfig?: StackNavigatorConfig
): NavigationContainer<*, *, *>;
declare type _TabViewConfig = {|
tabBarComponent?: React$ElementType,
@@ -862,30 +772,6 @@ declare module 'react-navigation' {
routeConfigs: NavigationRouteConfigMap,
config?: _TabNavigatorConfig
): NavigationContainer<*, *, *>;
declare export function createTabNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _TabNavigatorConfig
): NavigationContainer<*, *, *>;
/* TODO: fix the config for each of these tab navigator types */
declare export function createBottomTabNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _TabNavigatorConfig
): NavigationContainer<*, *, *>;
declare export function createMaterialTopTabNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _TabNavigatorConfig
): NavigationContainer<*, *, *>;
declare type _SwitchNavigatorConfig = {|
...NavigationSwitchRouterConfig,
|};
declare export function SwitchNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _SwitchNavigatorConfig
): NavigationContainer<*, *, *>;
declare export function createSwitchNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _SwitchNavigatorConfig
): NavigationContainer<*, *, *>;
declare type _DrawerViewConfig = {|
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
@@ -907,10 +793,6 @@ declare module 'react-navigation' {
routeConfigs: NavigationRouteConfigMap,
config?: _DrawerNavigatorConfig
): NavigationContainer<*, *, *>;
declare export function createDrawerNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _DrawerNavigatorConfig
): NavigationContainer<*, *, *>;
declare export function StackRouter(
routeConfigs: NavigationRouteConfigMap,
@@ -996,14 +878,12 @@ declare module 'react-navigation' {
vertical?: _SafeAreaViewForceInsetValue,
horizontal?: _SafeAreaViewForceInsetValue,
},
children?: React$Node,
children: React$Node,
style?: AnimatedViewStyleProp,
};
declare export var SafeAreaView: React$ComponentType<_SafeAreaViewProps>;
declare export var Header: React$ComponentType<HeaderProps> & {
HEIGHT: number,
};
declare export var Header: React$ComponentType<HeaderProps>;
declare type _HeaderTitleProps = {
children: React$Node,
@@ -1146,26 +1026,13 @@ declare module 'react-navigation' {
};
declare export var TabBarBottom: React$ComponentType<_TabBarBottomProps>;
declare export function withNavigation<Props: {}>(
Component: React$ComponentType<Props>
): React$ComponentType<
$Diff<
Props,
{
navigation: NavigationScreenProp<NavigationStateRoute> | void,
}
>
>;
declare export function withNavigationFocus<Props: {}>(
Component: React$ComponentType<Props>
): React$ComponentType<$Diff<Props, { isFocused: boolean | void }>>;
declare export function getNavigation<State: NavigationState, Options: {}>(
router: NavigationRouter<State, Options>,
state: State,
dispatch: NavigationDispatch,
actionSubscribers: Set<NavigationEventCallback>,
getScreenProps: () => {},
getCurrentNavigation: () => NavigationScreenProp<State>
): NavigationScreenProp<State>;
declare type _NavigationInjectedProps = {
navigation: NavigationScreenProp<NavigationStateRoute>,
};
declare export function withNavigation<T: {}>(
Component: React$ComponentType<T & _NavigationInjectedProps>
): React$ComponentType<T>;
declare export function withNavigationFocus<T: {}>(
Component: React$ComponentType<T & _NavigationInjectedProps>
): React$ComponentType<T>;
}

View File

@@ -1,13 +1,14 @@
{
"name": "react-navigation",
"version": "2.5.1",
"version": "1.2.1",
"description": "Routing and navigation for your React Native apps",
"main": "src/react-navigation.js",
"repository": {
"url": "git@github.com:react-navigation/react-navigation.git",
"type": "git"
},
"author": "Adam Miskiewicz <adam@sk3vy.com>, Eric Vicenti <ericvicenti@gmail.com>, Brent Vatne <brent@expo.io>",
"author":
"Adam Miskiewicz <adam@sk3vy.com>, Eric Vicenti <ericvicenti@gmail.com>",
"license": "BSD-2-Clause",
"scripts": {
"start": "npm run ios",
@@ -21,43 +22,38 @@
"format": "eslint --fix .",
"precommit": "lint-staged"
},
"files": [
"src"
],
"files": ["src"],
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"dependencies": {
"clamp": "^1.0.1",
"create-react-context": "^0.2.1",
"hoist-non-react-statics": "^2.2.0",
"path-to-regexp": "^1.7.0",
"react-lifecycles-compat": "^3",
"react-native-safe-area-view": "^0.8.0",
"react-navigation-deprecated-tab-navigator": "1.3.0",
"react-navigation-drawer": "0.4.1",
"react-navigation-tabs": "0.5.1"
"prop-types": "^15.5.10",
"react-native-drawer-layout-polyfill": "^1.3.2",
"react-native-safe-area-view": "^0.7.0",
"react-native-tab-view": "^0.0.74"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.25.0",
"babel-eslint": "^7.2.3",
"babel-jest": "^22.4.1",
"babel-jest": "^20.0.3",
"babel-preset-react-native": "^2.1.0",
"codecov": "^2.2.0",
"eslint": "^4.2.0",
"eslint-config-prettier": "^2.9.0",
"eslint-config-prettier": "^2.3.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-prettier": "^2.1.2",
"eslint-plugin-react": "^7.1.0",
"husky": "^0.14.3",
"jest": "^22.1.3",
"jest-expo": "^25.1.0",
"lint-staged": "^4.2.1",
"prettier": "^1.12.1",
"prettier-eslint": "^8.8.1",
"prettier": "^1.5.3",
"prettier-eslint": "^6.4.2",
"react": "16.2.0",
"react-native": "^0.52.0",
"react-native-vector-icons": "^4.2.0",
@@ -66,30 +62,14 @@
"jest": {
"notify": true,
"preset": "react-native",
"testRegex": "/__tests__/[^/]+-test\\.js$",
"setupFiles": [
"<rootDir>/jest-setup.js"
],
"testRegex": "./src/.*\\-test\\.js$",
"setupFiles": ["<rootDir>/jest-setup.js"],
"coverageDirectory": "./coverage/",
"collectCoverage": true,
"coverageReporters": [
"lcov"
],
"collectCoverageFrom": [
"src/**/*.js"
],
"coveragePathIgnorePatterns": [
"jest-setup.js"
],
"moduleNameMapper": {
"\\.png$": "<rootDir>/assetsTransformer.js"
},
"modulePathIgnorePatterns": [
"<rootDir>/examples/"
],
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation-deprecated-tab-navigator)"
]
"coverageReporters": ["lcov"],
"collectCoverageFrom": ["src/**/*.js"],
"coveragePathIgnorePatterns": ["jest-setup.js"],
"modulePathIgnorePatterns": ["examples"]
},
"lint-staged": {
"*.js": [

View File

@@ -4,6 +4,6 @@ set -eo pipefail
case $CIRCLE_NODE_INDEX in
0) yarn test && yarn codecov ;;
#1) cd examples/NavigationPlayground && yarn && yarn test ;;
1) yarn link && cd examples/NavigationPlayground && yarn && yarn link react-navigation && yarn test ;;
#2) cd examples/ReduxExample && yarn && yarn test ;;
esac

View File

@@ -1,15 +1,30 @@
const BACK = 'Navigation/BACK';
const INIT = 'Navigation/INIT';
const NAVIGATE = 'Navigation/NAVIGATE';
const POP = 'Navigation/POP';
const POP_TO_TOP = 'Navigation/POP_TO_TOP';
const PUSH = 'Navigation/PUSH';
const RESET = 'Navigation/RESET';
const REPLACE = 'Navigation/REPLACE';
const SET_PARAMS = 'Navigation/SET_PARAMS';
const URI = 'Navigation/URI';
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
const OPEN_DRAWER = 'Navigation/OPEN_DRAWER';
const CLOSE_DRAWER = 'Navigation/CLOSE_DRAWER';
const TOGGLE_DRAWER = 'Navigation/TOGGLE_DRAWER';
const back = (payload = {}) => ({
const createAction = (type, fn) => {
fn.toString = () => type;
return fn;
};
const back = createAction(BACK, (payload = {}) => ({
type: BACK,
key: payload.key,
immediate: payload.immediate,
});
}));
const init = (payload = {}) => {
const init = createAction(INIT, (payload = {}) => {
const action = {
type: INIT,
};
@@ -17,9 +32,9 @@ const init = (payload = {}) => {
action.params = payload.params;
}
return action;
};
});
const navigate = payload => {
const navigate = createAction(NAVIGATE, payload => {
const action = {
type: NAVIGATE,
routeName: payload.routeName,
@@ -34,24 +49,107 @@ const navigate = payload => {
action.key = payload.key;
}
return action;
};
});
const setParams = payload => ({
const pop = createAction(POP, payload => ({
type: POP,
n: payload && payload.n,
immediate: payload && payload.immediate,
}));
const popToTop = createAction(POP_TO_TOP, payload => ({
type: POP_TO_TOP,
immediate: payload && payload.immediate,
key: payload && payload.key,
}));
const push = createAction(PUSH, payload => {
const action = {
type: PUSH,
routeName: payload.routeName,
};
if (payload.params) {
action.params = payload.params;
}
if (payload.action) {
action.action = payload.action;
}
return action;
});
const reset = createAction(RESET, payload => ({
type: RESET,
index: payload.index,
key: payload.key,
actions: payload.actions,
}));
const replace = createAction(REPLACE, payload => ({
type: REPLACE,
key: payload.key,
newKey: payload.newKey,
params: payload.params,
action: payload.action,
routeName: payload.routeName,
immediate: payload.immediate,
}));
const setParams = createAction(SET_PARAMS, payload => ({
type: SET_PARAMS,
key: payload.key,
params: payload.params,
});
}));
const uri = createAction(URI, payload => ({
type: URI,
uri: payload.uri,
}));
const completeTransition = createAction(COMPLETE_TRANSITION, payload => ({
type: COMPLETE_TRANSITION,
key: payload && payload.key,
}));
const openDrawer = createAction(OPEN_DRAWER, payload => ({
type: OPEN_DRAWER,
}));
const closeDrawer = createAction(CLOSE_DRAWER, payload => ({
type: CLOSE_DRAWER,
}));
const toggleDrawer = createAction(TOGGLE_DRAWER, payload => ({
type: TOGGLE_DRAWER,
}));
export default {
// Action constants
BACK,
INIT,
NAVIGATE,
POP,
POP_TO_TOP,
PUSH,
RESET,
REPLACE,
SET_PARAMS,
URI,
COMPLETE_TRANSITION,
OPEN_DRAWER,
CLOSE_DRAWER,
TOGGLE_DRAWER,
// Action creators
back,
init,
navigate,
pop,
popToTop,
push,
reset,
replace,
setParams,
uri,
completeTransition,
openDrawer,
closeDrawer,
toggleDrawer,
};

View File

@@ -21,7 +21,7 @@ const StateUtils = {
* routes of the navigation state, or -1 if it is not present.
*/
indexOf(state, key) {
return state.routes.findIndex(route => route.key === key);
return state.routes.map(route => route.key).indexOf(key);
},
/**
@@ -116,23 +116,8 @@ const StateUtils = {
/**
* Replace a route by a key.
* Note that this moves the index to the position to where the new route in the
* stack is at and updates the routes array accordingly.
*/
replaceAndPrune(state, key, route) {
const index = StateUtils.indexOf(state, key);
const replaced = StateUtils.replaceAtIndex(state, index, route);
return {
...replaced,
routes: replaced.routes.slice(0, index + 1),
};
},
/**
* Replace a route by a key.
* Note that this moves the index to the position to where the new route in the
* stack is at. Does not prune the routes.
* Note that this moves the index to the positon to where the new route in the
* stack is at.
*/
replaceAt(state, key, route) {
const index = StateUtils.indexOf(state, key);
@@ -152,7 +137,7 @@ const StateUtils = {
route.key
);
if (state.routes[index] === route && index === state.index) {
if (state.routes[index] === route) {
return state;
}
@@ -168,7 +153,7 @@ const StateUtils = {
/**
* Resets all routes.
* Note that this moves the index to the position to where the last route in the
* Note that this moves the index to the positon to where the last route in the
* stack is at if the param `index` isn't provided.
*/
reset(state, routes, index) {

View File

@@ -1,10 +1,11 @@
import NavigationActions from '../NavigationActions';
describe('generic navigation actions', () => {
describe('actions', () => {
const params = { foo: 'bar' };
const navigateAction = NavigationActions.navigate({ routeName: 'another' });
it('exports back action and type', () => {
expect(NavigationActions.back.toString()).toEqual(NavigationActions.BACK);
expect(NavigationActions.back()).toEqual({ type: NavigationActions.BACK });
expect(NavigationActions.back({ key: 'test' })).toEqual({
type: NavigationActions.BACK,
@@ -13,6 +14,7 @@ describe('generic navigation actions', () => {
});
it('exports init action and type', () => {
expect(NavigationActions.init.toString()).toEqual(NavigationActions.INIT);
expect(NavigationActions.init()).toEqual({ type: NavigationActions.INIT });
expect(NavigationActions.init({ params })).toEqual({
type: NavigationActions.INIT,
@@ -21,6 +23,9 @@ describe('generic navigation actions', () => {
});
it('exports navigate action and type', () => {
expect(NavigationActions.navigate.toString()).toEqual(
NavigationActions.NAVIGATE
);
expect(NavigationActions.navigate({ routeName: 'test' })).toEqual({
type: NavigationActions.NAVIGATE,
routeName: 'test',
@@ -42,7 +47,36 @@ describe('generic navigation actions', () => {
});
});
it('exports reset action and type', () => {
expect(NavigationActions.reset.toString()).toEqual(NavigationActions.RESET);
expect(NavigationActions.reset({ index: 0, actions: [] })).toEqual({
type: NavigationActions.RESET,
index: 0,
actions: [],
});
expect(
NavigationActions.reset({
index: 0,
key: 'test',
actions: [navigateAction],
})
).toEqual({
type: NavigationActions.RESET,
index: 0,
key: 'test',
actions: [
{
type: NavigationActions.NAVIGATE,
routeName: 'another',
},
],
});
});
it('exports setParams action and type', () => {
expect(NavigationActions.setParams.toString()).toEqual(
NavigationActions.SET_PARAMS
);
expect(
NavigationActions.setParams({
key: 'test',
@@ -54,4 +88,12 @@ describe('generic navigation actions', () => {
params,
});
});
it('exports uri action and type', () => {
expect(NavigationActions.uri.toString()).toEqual(NavigationActions.URI);
expect(NavigationActions.uri({ uri: 'http://google.com' })).toEqual({
type: NavigationActions.URI,
uri: 'http://google.com',
});
});
});

View File

@@ -1,53 +1,46 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import 'react-native';
import renderer from 'react-test-renderer';
import NavigationActions from '../NavigationActions';
import createStackNavigator from '../navigators/createStackNavigator';
import createNavigationContainer, {
_TESTING_ONLY_reset_container_count,
} from '../createNavigationContainer';
import StackNavigator from '../navigators/createStackNavigator';
const FooScreen = () => <div />;
const BarScreen = () => <div />;
const BazScreen = () => <div />;
const CarScreen = () => <div />;
const DogScreen = () => <div />;
const ElkScreen = () => <div />;
const NavigationContainer = StackNavigator(
{
foo: {
screen: FooScreen,
},
bar: {
screen: BarScreen,
},
baz: {
screen: BazScreen,
},
car: {
screen: CarScreen,
},
dog: {
screen: DogScreen,
},
elk: {
screen: ElkScreen,
},
},
{
initialRouteName: 'foo',
}
);
jest.useFakeTimers();
describe('NavigationContainer', () => {
jest.useFakeTimers();
beforeEach(() => {
_TESTING_ONLY_reset_container_count();
});
const FooScreen = () => <div />;
const BarScreen = () => <div />;
const BazScreen = () => <div />;
const CarScreen = () => <div />;
const DogScreen = () => <div />;
const ElkScreen = () => <div />;
const Stack = createStackNavigator(
{
foo: {
screen: FooScreen,
},
bar: {
screen: BarScreen,
},
baz: {
screen: BazScreen,
},
car: {
screen: CarScreen,
},
dog: {
screen: DogScreen,
},
elk: {
screen: ElkScreen,
},
},
{
initialRouteName: 'foo',
}
);
const NavigationContainer = createNavigationContainer(Stack);
describe('state.nav', () => {
it("should be preloaded with the router's initial state", () => {
const navigationContainer = renderer
@@ -205,61 +198,4 @@ describe('NavigationContainer', () => {
});
});
});
describe('warnings', () => {
function spyConsole() {
let spy = {};
beforeEach(() => {
spy.console = jest.spyOn(console, 'warn').mockImplementation(() => {});
});
afterEach(() => {
spy.console.mockRestore();
});
return spy;
}
describe('detached navigators', () => {
beforeEach(() => {
_TESTING_ONLY_reset_container_count();
});
let spy = spyConsole();
it('warns when you render more than one container explicitly', () => {
class BlankScreen extends React.Component {
render() {
return <View />;
}
}
class RootScreen extends React.Component {
render() {
return (
<View>
<ChildNavigator />
</View>
);
}
}
const ChildNavigator = createNavigationContainer(
createStackNavigator({
Child: BlankScreen,
})
);
const RootStack = createNavigationContainer(
createStackNavigator({
Root: RootScreen,
})
);
renderer.create(<RootStack />).toJSON();
expect(spy).toMatchSnapshot();
});
});
});
});

View File

@@ -8,7 +8,6 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }],
isTransitioning: false,
};
expect(NavigationStateUtils.get(state, 'a')).toEqual({
key: 'a',
@@ -21,7 +20,6 @@ describe('StateUtils', () => {
const state = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
expect(NavigationStateUtils.indexOf(state, 'a')).toBe(0);
expect(NavigationStateUtils.indexOf(state, 'b')).toBe(1);
@@ -32,7 +30,6 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
expect(NavigationStateUtils.has(state, 'b')).toBe(true);
expect(NavigationStateUtils.has(state, 'c')).toBe(false);
@@ -43,11 +40,9 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }],
isTransitioning: false,
};
const newState = {
index: 1,
isTransitioning: false,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.push(state, { key: 'b', routeName })).toEqual(
@@ -59,7 +54,6 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }],
isTransitioning: false,
};
expect(() =>
NavigationStateUtils.push(state, { key: 'a', routeName })
@@ -71,12 +65,10 @@ describe('StateUtils', () => {
const state = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
const newState = {
index: 0,
routes: [{ key: 'a', routeName }],
isTransitioning: false,
};
expect(NavigationStateUtils.pop(state)).toEqual(newState);
});
@@ -85,7 +77,6 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }],
isTransitioning: false,
};
expect(NavigationStateUtils.pop(state)).toBe(state);
});
@@ -95,12 +86,10 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
expect(NavigationStateUtils.jumpToIndex(state, 0)).toBe(state);
expect(NavigationStateUtils.jumpToIndex(state, 1)).toEqual(newState);
@@ -110,7 +99,6 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
expect(() => NavigationStateUtils.jumpToIndex(state, 2)).toThrow();
});
@@ -119,12 +107,10 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
expect(NavigationStateUtils.jumpTo(state, 'a')).toBe(state);
expect(NavigationStateUtils.jumpTo(state, 'b')).toEqual(newState);
@@ -134,7 +120,6 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
expect(() => NavigationStateUtils.jumpTo(state, 'c')).toThrow();
});
@@ -143,12 +128,10 @@ describe('StateUtils', () => {
const state = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
const newState = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
expect(NavigationStateUtils.back(state)).toEqual(newState);
expect(NavigationStateUtils.back(newState)).toBe(newState);
@@ -158,12 +141,10 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
expect(NavigationStateUtils.forward(state)).toEqual(newState);
expect(NavigationStateUtils.forward(newState)).toBe(newState);
@@ -174,12 +155,10 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
isTransitioning: false,
};
expect(
NavigationStateUtils.replaceAt(state, 'b', { key: 'c', routeName })
@@ -190,27 +169,24 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
isTransitioning: false,
};
expect(
NavigationStateUtils.replaceAtIndex(state, 1, { key: 'c', routeName })
).toEqual(newState);
});
it('Returns the state with updated index if route is unchanged but index changes', () => {
it('Returns the state if index matches the route', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
expect(
NavigationStateUtils.replaceAtIndex(state, 1, state.routes[1])
).toEqual({ ...state, index: 1 });
).toEqual(state);
});
// Reset
@@ -218,12 +194,10 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
const newState = {
index: 1,
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
isTransitioning: false,
};
expect(
NavigationStateUtils.reset(state, [
@@ -241,12 +215,10 @@ describe('StateUtils', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
isTransitioning: false,
};
const newState = {
index: 0,
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
isTransitioning: false,
};
expect(
NavigationStateUtils.reset(

View File

@@ -1,13 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NavigationContainer warnings detached navigators warns when you render more than one container explicitly 1`] = `
Object {
"console": [MockFunction] {
"calls": Array [
Array [
"You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator. Full details at: https://reactnavigation.org/docs/common-mistakes.html#explicitly-rendering-more-than-one-navigator",
],
],
},
}
`;

View File

@@ -0,0 +1,118 @@
import NavigationActions from '../NavigationActions';
import addNavigationHelpers from '../addNavigationHelpers';
const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
remove: () => {},
});
describe('addNavigationHelpers', () => {
it('handles Back action', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { key: 'A', routeName: 'Home' },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).goBack('A')
).toEqual(true);
expect(mockedDispatch).toBeCalledWith({
type: NavigationActions.BACK,
key: 'A',
});
expect(mockedDispatch.mock.calls.length).toBe(1);
});
it('handles Back action when the key is not defined', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { routeName: 'Home' },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).goBack()
).toEqual(true);
expect(mockedDispatch).toBeCalledWith({ type: NavigationActions.BACK });
expect(mockedDispatch.mock.calls.length).toBe(1);
});
it('handles Navigate action', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { routeName: 'Home' },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).navigate('Profile', { name: 'Matt' })
).toEqual(true);
expect(mockedDispatch).toBeCalledWith({
type: NavigationActions.NAVIGATE,
params: { name: 'Matt' },
routeName: 'Profile',
});
expect(mockedDispatch.mock.calls.length).toBe(1);
});
it('handles SetParams action', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { key: 'B', routeName: 'Settings' },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).setParams({ notificationsEnabled: 'yes' })
).toEqual(true);
expect(mockedDispatch).toBeCalledWith({
type: NavigationActions.SET_PARAMS,
key: 'B',
params: { notificationsEnabled: 'yes' },
});
expect(mockedDispatch.mock.calls.length).toBe(1);
});
it('handles GetParams action', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { key: 'B', routeName: 'Settings', params: { name: 'Peter' } },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).getParam('name', 'Brent')
).toEqual('Peter');
});
it('handles GetParams action with default param', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { key: 'B', routeName: 'Settings' },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).getParam('name', 'Brent')
).toEqual('Brent');
});
it('handles GetParams action with param value as null', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { key: 'B', routeName: 'Settings', params: { name: null } },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).getParam('name')
).toEqual(null);
});
});

View File

@@ -1,102 +0,0 @@
import getNavigation from '../getNavigation';
test('getNavigation provides default action helpers', () => {
const router = {
getActionCreators: () => ({}),
getStateForAction(action, lastState = {}) {
return lastState;
},
};
const dispatch = jest.fn();
const topNav = getNavigation(
router,
{},
dispatch,
new Set(),
() => ({}),
() => topNav
);
topNav.navigate('GreatRoute');
expect(dispatch.mock.calls.length).toBe(1);
expect(dispatch.mock.calls[0][0].type).toBe('Navigation/NAVIGATE');
expect(dispatch.mock.calls[0][0].routeName).toBe('GreatRoute');
});
test('getNavigation provides router action helpers', () => {
const router = {
getActionCreators: () => ({
foo: bar => ({ type: 'FooBarAction', bar }),
}),
getStateForAction(action, lastState = {}) {
return lastState;
},
};
const dispatch = jest.fn();
const topNav = getNavigation(
router,
{},
dispatch,
new Set(),
() => ({}),
() => topNav
);
topNav.foo('Great');
expect(dispatch.mock.calls.length).toBe(1);
expect(dispatch.mock.calls[0][0].type).toBe('FooBarAction');
expect(dispatch.mock.calls[0][0].bar).toBe('Great');
});
test('getNavigation get child navigation with router', () => {
const actionSubscribers = new Set();
let navigation = null;
const routerA = {
getActionCreators: () => ({}),
getStateForAction(action, lastState = {}) {
return lastState;
},
};
const router = {
childRouters: {
RouteA: routerA,
},
getActionCreators: () => ({}),
getStateForAction(action, lastState = {}) {
return lastState;
},
};
const initState = {
index: 0,
routes: [
{
key: 'a',
routeName: 'RouteA',
routes: [{ key: 'c', routeName: 'RouteC' }],
index: 0,
},
{ key: 'b', routeName: 'RouteB' },
],
};
const topNav = getNavigation(
router,
initState,
() => {},
actionSubscribers,
() => ({}),
() => navigation
);
const childNavA = topNav.getChildNavigation('a');
expect(childNavA.router).toBe(routerA);
});

View File

@@ -0,0 +1,93 @@
/* Helpers for navigation */
import NavigationActions from './NavigationActions';
import invariant from './utils/invariant';
export default function(navigation) {
return {
...navigation,
goBack: key => {
let actualizedKey = key;
if (key === undefined && navigation.state.key) {
invariant(
typeof navigation.state.key === 'string',
'key should be a string'
);
actualizedKey = navigation.state.key;
}
return navigation.dispatch(
NavigationActions.back({ key: actualizedKey })
);
},
navigate: (navigateTo, params, action) => {
if (typeof navigateTo === 'string') {
return navigation.dispatch(
NavigationActions.navigate({ routeName: navigateTo, params, action })
);
}
invariant(
typeof navigateTo === 'object',
'Must navigateTo an object or a string'
);
invariant(
params == null,
'Params must not be provided to .navigate() when specifying an object'
);
invariant(
action == null,
'Child action must not be provided to .navigate() when specifying an object'
);
return navigation.dispatch(NavigationActions.navigate(navigateTo));
},
pop: (n, params) =>
navigation.dispatch(
NavigationActions.pop({ n, immediate: params && params.immediate })
),
popToTop: params =>
navigation.dispatch(
NavigationActions.popToTop({ immediate: params && params.immediate })
),
/**
* For updating current route params. For example the nav bar title and
* buttons are based on the route params.
* This means `setParams` can be used to update nav bar for example.
*/
setParams: params => {
invariant(
navigation.state.key && typeof navigation.state.key === 'string',
'setParams cannot be called by root navigator'
);
const key = navigation.state.key;
return navigation.dispatch(NavigationActions.setParams({ params, key }));
},
getParam: (paramName, defaultValue) => {
const params = navigation.state.params;
if (params && paramName in params) {
return params[paramName];
}
return defaultValue;
},
push: (routeName, params, action) =>
navigation.dispatch(
NavigationActions.push({ routeName, params, action })
),
replace: (routeName, params, action) =>
navigation.dispatch(
NavigationActions.replace({
routeName,
params,
action,
key: navigation.state.key,
})
),
openDrawer: () => navigation.dispatch(NavigationActions.openDrawer()),
closeDrawer: () => navigation.dispatch(NavigationActions.closeDrawer()),
toggleDrawer: () => navigation.dispatch(NavigationActions.toggleDrawer()),
};
}

View File

@@ -1,54 +1,9 @@
import React from 'react';
import { AsyncStorage, Linking, Platform } from 'react-native';
import { polyfill } from 'react-lifecycles-compat';
import { Linking } from 'react-native';
import { BackHandler } from './PlatformHelpers';
import NavigationActions from './NavigationActions';
import getNavigation from './getNavigation';
import addNavigationHelpers from './addNavigationHelpers';
import invariant from './utils/invariant';
import docsUrl from './utils/docsUrl';
function isStateful(props) {
return !props.navigation;
}
function validateProps(props) {
if (isStateful(props)) {
return;
}
const { navigation, screenProps, ...containerProps } = props;
const keys = Object.keys(containerProps);
if (keys.length !== 0) {
throw new Error(
'This navigator has both navigation and container props, so it is ' +
`unclear if it should own its own state. Remove props: "${keys.join(
', '
)}" ` +
'if the navigator should get its state from the navigation prop. If the ' +
'navigator should maintain its own state, do not pass a navigation prop.'
);
}
}
// Track the number of stateful container instances. Warn if >0 and not using the
// detached prop to explicitly acknowledge the behavior. We should deprecated implicit
// stateful navigation containers in a future release and require a provider style pattern
// instead in order to eliminate confusion entirely.
let _statefulContainerCount = 0;
export function _TESTING_ONLY_reset_container_count() {
_statefulContainerCount = 0;
}
// We keep a global flag to catch errors during the state persistence hydrating scenario.
// The innermost navigator who catches the error will dispatch a new init action.
let _reactNavigationIsHydratingState = false;
// Unfortunate to use global state here, but it seems necessesary for the time
// being. There seems to be some problems with cascading componentDidCatch
// handlers. Ideally the inner non-stateful navigator catches the error and
// re-throws it, to be caught by the top-level stateful navigator.
/**
* Create an HOC that injects the navigation and manages the navigation state
@@ -63,17 +18,12 @@ export default function createNavigationContainer(Component) {
static router = Component.router;
static navigationOptions = null;
static getDerivedStateFromProps(nextProps, prevState) {
validateProps(nextProps);
return null;
}
_actionEventSubscribers = new Set();
constructor(props) {
super(props);
validateProps(props);
this._validateProps(props);
this._initialAction = NavigationActions.init();
@@ -91,21 +41,14 @@ export default function createNavigationContainer(Component) {
}
this.state = {
nav:
this._isStateful() && !props.persistenceKey
? Component.router.getStateForAction(this._initialAction)
: null,
nav: this._isStateful()
? Component.router.getStateForAction(this._initialAction)
: null,
};
}
_renderLoading() {
return this.props.renderLoadingExperimental
? this.props.renderLoadingExperimental()
: null;
}
_isStateful() {
return isStateful(this.props);
return !this.props.navigation;
}
_validateProps(props) {
@@ -184,198 +127,98 @@ export default function createNavigationContainer(Component) {
}
}
componentWillReceiveProps(nextProps) {
this._validateProps(nextProps);
}
componentDidUpdate() {
// Clear cached _navState every tick
if (this._navState === this.state.nav) {
this._navState = null;
// Clear cached _nav every tick
if (this._nav === this.state.nav) {
this._nav = null;
}
}
async componentDidMount() {
componentDidMount() {
this._isMounted = true;
if (!this._isStateful()) {
return;
}
if (__DEV__ && !this.props.detached) {
if (_statefulContainerCount > 0) {
// Temporarily only show this on iOS due to this issue:
// https://github.com/react-navigation/react-navigation/issues/4196#issuecomment-390827829
if (Platform.OS === 'ios') {
console.warn(
`You should only render one navigator explicitly in your app, and other navigators should by rendered by including them in that navigator. Full details at: ${docsUrl(
'common-mistakes.html#explicitly-rendering-more-than-one-navigator'
)}`
);
}
}
}
_statefulContainerCount++;
Linking.addEventListener('url', this._handleOpenURL);
// Pull out anything that can impact state
const { persistenceKey } = this.props;
const startupStateJSON =
persistenceKey && (await AsyncStorage.getItem(persistenceKey));
const url = await Linking.getInitialURL();
const parsedUrl = url && this._urlToPathAndParams(url);
Linking.getInitialURL().then(url => url && this._handleOpenURL({ url }));
// Initialize state. This must be done *after* any async code
// so we don't end up with a different value for this.state.nav
// due to changes while async function was resolving
let action = this._initialAction;
let startupState = this.state.nav;
if (!startupState) {
!!process.env.REACT_NAV_LOGGING &&
console.log('Init new Navigation State');
startupState = Component.router.getStateForAction(action);
}
// Pull persisted state from AsyncStorage
if (startupStateJSON) {
try {
startupState = JSON.parse(startupStateJSON);
_reactNavigationIsHydratingState = true;
} catch (e) {}
}
// Pull state out of URL
if (parsedUrl) {
const { path, params } = parsedUrl;
const urlAction = Component.router.getActionForPathAndParams(
path,
params
);
if (urlAction) {
!!process.env.REACT_NAV_LOGGING &&
console.log('Applying Navigation Action for Initial URL:', url);
action = urlAction;
startupState = Component.router.getStateForAction(
urlAction,
startupState
);
}
}
const dispatchActions = () =>
this._actionEventSubscribers.forEach(subscriber =>
subscriber({
type: 'action',
action,
state: this.state.nav,
lastState: null,
})
);
if (startupState === this.state.nav) {
dispatchActions();
return;
}
this.setState({ nav: startupState }, () => {
_reactNavigationIsHydratingState = false;
dispatchActions();
});
this._actionEventSubscribers.forEach(subscriber =>
subscriber({
type: 'action',
action: this._initialAction,
state: this.state.nav,
lastState: null,
})
);
}
componentDidCatch(e, errorInfo) {
if (_reactNavigationIsHydratingState) {
_reactNavigationIsHydratingState = false;
console.warn(
'Uncaught exception while starting app from persisted navigation state! Trying to render again with a fresh navigation state..'
);
this.dispatch(NavigationActions.init());
} else {
throw e;
}
}
_persistNavigationState = async nav => {
const { persistenceKey } = this.props;
if (!persistenceKey) {
return;
}
await AsyncStorage.setItem(persistenceKey, JSON.stringify(nav));
};
componentWillUnmount() {
this._isMounted = false;
Linking.removeEventListener('url', this._handleOpenURL);
this.subs && this.subs.remove();
if (this._isStateful()) {
_statefulContainerCount--;
}
}
// Per-tick temporary storage for state.nav
dispatch = action => {
if (this.props.navigation) {
return this.props.navigation.dispatch(action);
if (!this._isStateful()) {
return false;
}
// navState will have the most up-to-date value, because setState sometimes behaves asyncronously
this._navState = this._navState || this.state.nav;
const lastNavState = this._navState;
invariant(lastNavState, 'should be set in constructor if stateful');
const reducedState = Component.router.getStateForAction(
action,
lastNavState
);
const navState = reducedState === null ? lastNavState : reducedState;
this._nav = this._nav || this.state.nav;
const oldNav = this._nav;
invariant(oldNav, 'should be set in constructor if stateful');
const nav = Component.router.getStateForAction(action, oldNav);
const dispatchActionEvents = () => {
this._actionEventSubscribers.forEach(subscriber =>
subscriber({
type: 'action',
action,
state: navState,
lastState: lastNavState,
state: nav,
lastState: oldNav,
})
);
};
if (reducedState === null) {
// The router will return null when action has been handled and the state hasn't changed.
// dispatch returns true when something has been handled.
dispatchActionEvents();
return true;
}
if (navState !== lastNavState) {
if (nav && nav !== oldNav) {
// Cache updates to state.nav during the tick to ensure that subsequent calls will not discard this change
this._navState = navState;
this.setState({ nav: navState }, () => {
this._onNavigationStateChange(lastNavState, navState, action);
this._nav = nav;
this.setState({ nav }, () => {
this._onNavigationStateChange(oldNav, nav, action);
dispatchActionEvents();
this._persistNavigationState(navState);
});
return true;
} else {
dispatchActionEvents();
}
dispatchActionEvents();
return false;
};
_getScreenProps = () => this.props.screenProps;
render() {
let navigation = this.props.navigation;
if (this._isStateful()) {
const navState = this.state.nav;
if (!navState) {
return this._renderLoading();
}
if (!this._navigation || this._navigation.state !== navState) {
this._navigation = getNavigation(
Component.router,
navState,
this.dispatch,
this._actionEventSubscribers,
this._getScreenProps,
() => this._navigation
);
const nav = this.state.nav;
invariant(nav, 'should be set in constructor if stateful');
if (!this._navigation || this._navigation.state !== nav) {
this._navigation = addNavigationHelpers({
dispatch: this.dispatch,
state: nav,
addListener: (eventName, handler) => {
if (eventName !== 'action') {
return { remove: () => {} };
}
this._actionEventSubscribers.add(handler);
return {
remove: () => {
this._actionEventSubscribers.delete(handler);
},
};
},
});
}
navigation = this._navigation;
}
@@ -384,5 +227,5 @@ export default function createNavigationContainer(Component) {
}
}
return polyfill(NavigationContainer);
return NavigationContainer;
}

View File

@@ -84,7 +84,7 @@ export default function getChildEventSubscriber(addListener, key) {
action,
type: eventName,
};
const isTransitioning = !!state && state.isTransitioning;
const isTransitioning = !!state && !!state.transitioningFromKey;
const previouslyLastEmittedEvent = lastEmittedEvent;
@@ -138,14 +138,11 @@ export default function getChildEventSubscriber(addListener, key) {
emit((lastEmittedEvent = 'didBlur'), childPayload);
}
}
if (lastEmittedEvent === 'didBlur' && !newRoute) {
removeAll();
}
})
);
return {
removeAll,
addListener(eventName, eventHandler) {
const subscribers = getChildSubscribers(eventName);
if (!subscribers) {

View File

@@ -1,102 +0,0 @@
import getChildEventSubscriber from './getChildEventSubscriber';
import getChildRouter from './getChildRouter';
import invariant from './utils/invariant';
const createParamGetter = route => (paramName, defaultValue) => {
const params = route.params;
if (params && paramName in params) {
return params[paramName];
}
return defaultValue;
};
function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
const children =
navigation._childrenNavigation || (navigation._childrenNavigation = {});
const childRoute = navigation.state.routes.find(r => r.key === childKey);
if (children[childKey] && children[childKey].state === childRoute) {
return children[childKey];
}
const childRouter = getChildRouter(navigation.router, childRoute.routeName);
// If the route has children, we'll use this to pass in to the action creators
// for the childRouter so that any action that depends on the active route will
// behave as expected. We don't explicitly require that routers implement routes
// and index properties, but if we did then we would put an invariant here to
// ensure that a focusedGrandChildRoute exists if childRouter is defined.
const focusedGrandChildRoute =
childRoute.routes && typeof childRoute.index === 'number'
? childRoute.routes[childRoute.index]
: null;
const actionCreators = {
...navigation.actions,
...navigation.router.getActionCreators(childRoute, navigation.state.key),
...(childRouter
? childRouter.getActionCreators(focusedGrandChildRoute, childRoute.key)
: {}),
};
const actionHelpers = {};
Object.keys(actionCreators).forEach(actionName => {
actionHelpers[actionName] = (...args) => {
const actionCreator = actionCreators[actionName];
const action = actionCreator(...args);
return navigation.dispatch(action);
};
});
if (children[childKey]) {
children[childKey] = {
...children[childKey],
...actionHelpers,
state: childRoute,
router: childRouter,
actions: actionCreators,
getParam: createParamGetter(childRoute),
};
return children[childKey];
}
const childSubscriber = getChildEventSubscriber(
navigation.addListener,
childKey
);
children[childKey] = {
...actionHelpers,
state: childRoute,
router: childRouter,
actions: actionCreators,
getParam: createParamGetter(childRoute),
getChildNavigation: grandChildKey =>
getChildNavigation(children[childKey], grandChildKey, () =>
getCurrentParentNavigation().getChildNavigation(childKey)
),
isFocused: () => {
const currentNavigation = getCurrentParentNavigation();
const { routes, index } = currentNavigation.state;
if (!currentNavigation.isFocused()) {
return false;
}
if (routes[index].key === childKey) {
return true;
}
return false;
},
dispatch: navigation.dispatch,
getScreenProps: navigation.getScreenProps,
dangerouslyGetParent: getCurrentParentNavigation,
addListener: childSubscriber.addListener,
};
return children[childKey];
}
export default getChildNavigation;

View File

@@ -1,9 +0,0 @@
export default function getChildRouter(router, routeName) {
if (router.childRouters && router.childRouters[routeName]) {
return router.childRouters[routeName];
}
const Component = router.getComponentForRouteName(routeName);
return Component.router;
}

View File

@@ -1,54 +0,0 @@
import getNavigationActionCreators from './routers/getNavigationActionCreators';
import getChildNavigation from './getChildNavigation';
export default function getNavigation(
router,
state,
dispatch,
actionSubscribers,
getScreenProps,
getCurrentNavigation
) {
const actions = router.getActionCreators(state, null);
const navigation = {
actions,
router,
state,
dispatch,
getScreenProps,
getChildNavigation: childKey =>
getChildNavigation(navigation, childKey, getCurrentNavigation),
isFocused: childKey => {
const { routes, index } = getCurrentNavigation().state;
if (childKey == null || routes[index].key === childKey) {
return true;
}
return false;
},
addListener: (eventName, handler) => {
if (eventName !== 'action') {
return { remove: () => {} };
}
actionSubscribers.add(handler);
return {
remove: () => {
actionSubscribers.delete(handler);
},
};
},
dangerouslyGetParent: () => null,
};
const actionCreators = {
...getNavigationActionCreators(navigation.state),
...actions,
};
Object.keys(actionCreators).forEach(actionName => {
navigation[actionName] = (...args) =>
navigation.dispatch(actionCreators[actionName](...args));
});
return navigation;
}

View File

@@ -0,0 +1,71 @@
import React from 'react';
import { Dimensions, Platform, ScrollView } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import createNavigator from './createNavigator';
import createNavigationContainer from '../createNavigationContainer';
import DrawerRouter from '../routers/DrawerRouter';
import DrawerScreen from '../views/Drawer/DrawerScreen';
import DrawerView from '../views/Drawer/DrawerView';
import DrawerItems from '../views/Drawer/DrawerNavigatorItems';
// A stack navigators props are the intersection between
// the base navigator props (navgiation, screenProps, etc)
// and the view's props
const defaultContentComponent = props => (
<ScrollView alwaysBounceVertical={false}>
<SafeAreaView forceInset={{ top: 'always', horizontal: 'never' }}>
<DrawerItems {...props} />
</SafeAreaView>
</ScrollView>
);
const DefaultDrawerConfig = {
drawerWidth: () => {
/*
* Default drawer width is screen width - header height
* with a max width of 280 on mobile and 320 on tablet
* https://material.io/guidelines/patterns/navigation-drawer.html
*/
const { height, width } = Dimensions.get('window');
const smallerAxisSize = Math.min(height, width);
const isLandscape = width > height;
const isTablet = smallerAxisSize >= 600;
const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56;
const maxWidth = isTablet ? 320 : 280;
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
},
contentComponent: defaultContentComponent,
drawerPosition: 'left',
drawerBackgroundColor: 'white',
useNativeAnimations: true,
};
const DrawerNavigator = (routeConfigs, config = {}) => {
const mergedConfig = { ...DefaultDrawerConfig, ...config };
const {
order,
paths,
initialRouteName,
backBehavior,
...drawerConfig
} = mergedConfig;
const routerConfig = {
order,
paths,
initialRouteName,
backBehavior,
};
const drawerRouter = DrawerRouter(routeConfigs, routerConfig);
const navigator = createNavigator(DrawerView, drawerRouter, drawerConfig);
return createNavigationContainer(navigator);
};
export default DrawerNavigator;

View File

@@ -0,0 +1,33 @@
import React, { Component } from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
import DrawerNavigator from '../DrawerNavigator';
class HomeScreen extends Component {
static navigationOptions = ({ navigation }) => ({
title: `Welcome ${
navigation.state.params ? navigation.state.params.user : 'anonymous'
}`,
gesturesEnabled: true,
});
render() {
return null;
}
}
const routeConfig = {
Home: {
screen: HomeScreen,
},
};
describe('DrawerNavigator', () => {
it('renders successfully', () => {
const MyDrawerNavigator = DrawerNavigator(routeConfig);
const rendered = renderer.create(<MyDrawerNavigator />).toJSON();
expect(rendered).toMatchSnapshot();
});
});

View File

@@ -1,40 +0,0 @@
import React from 'react';
import renderer from 'react-test-renderer';
import StackNavigator from '../createContainedStackNavigator';
const SubNavigator = StackNavigator({
Home: {
screen: () => null,
},
});
const NavNestedDirect = StackNavigator({
Sub: {
screen: SubNavigator,
},
});
const NavNestedIndirect = StackNavigator({
Sub: {
// eslint-disable-next-line react/display-name
screen: props => <SubNavigator {...props} />,
},
});
/* Prevent React error boundaries from swallowing the errors */
NavNestedIndirect.prototype.componentDidCatch = null;
SubNavigator.prototype.componentDidCatch = null;
describe('Nested navigators', () => {
it('renders succesfully as direct child', () => {
const rendered = renderer.create(<NavNestedDirect />).toJSON();
expect(rendered).toMatchSnapshot();
});
it('throw when trying to pass navigation prop', () => {
const tryRender = () => {
renderer.create(<NavNestedIndirect />);
};
expect(tryRender).toThrowErrorMatchingSnapshot();
});
});

View File

@@ -2,9 +2,7 @@ import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import renderer from 'react-test-renderer';
import StackNavigator from '../createContainedStackNavigator';
import withNavigation from '../../views/withNavigation';
import { _TESTING_ONLY_reset_container_count } from '../../createNavigationContainer';
import StackNavigator from '../createStackNavigator';
const styles = StyleSheet.create({
header: {
@@ -33,10 +31,6 @@ const routeConfig = {
};
describe('StackNavigator', () => {
beforeEach(() => {
_TESTING_ONLY_reset_container_count();
});
it('renders successfully', () => {
const MyStackNavigator = StackNavigator(routeConfig);
const rendered = renderer.create(<MyStackNavigator />).toJSON();
@@ -57,37 +51,4 @@ describe('StackNavigator', () => {
expect(rendered).toMatchSnapshot();
});
it('passes navigation to headerRight when wrapped in withNavigation', () => {
const spy = jest.fn();
class TestComponent extends React.Component {
render() {
return <View>{this.props.onPress(this.props.navigation)}</View>;
}
}
const TestComponentWithNavigation = withNavigation(TestComponent);
class A extends React.Component {
static navigationOptions = {
headerRight: <TestComponentWithNavigation onPress={spy} />,
};
render() {
return <View />;
}
}
const Nav = StackNavigator({ A: { screen: A } });
renderer.create(<Nav />);
expect(spy).toBeCalledWith(
expect.objectContaining({
navigate: expect.any(Function),
addListener: expect.any(Function),
})
);
});
});

View File

@@ -1,18 +0,0 @@
import React, { Component } from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
import SwitchNavigator from '../createContainedSwitchNavigator';
const A = () => <View />;
const B = () => <View />;
const routeConfig = { A, B };
describe('SwitchNavigator', () => {
it('renders successfully', () => {
const MySwitchNavigator = SwitchNavigator(routeConfig);
const rendered = renderer.create(<MySwitchNavigator />).toJSON();
expect(rendered).toMatchSnapshot();
});
});

View File

@@ -2,9 +2,7 @@ import React, { Component } from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
const {
createTabNavigator,
} = require('react-navigation-deprecated-tab-navigator');
import TabNavigator from '../createTabNavigator';
class HomeScreen extends Component {
static navigationOptions = ({ navigation }) => ({
@@ -27,7 +25,7 @@ const routeConfig = {
describe('TabNavigator', () => {
it('renders successfully', () => {
const MyTabNavigator = createTabNavigator(routeConfig);
const MyTabNavigator = TabNavigator(routeConfig);
const rendered = renderer.create(<MyTabNavigator />).toJSON();
expect(rendered).toMatchSnapshot();

View File

@@ -0,0 +1,243 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DrawerNavigator renders successfully 1`] = `
<View
onMoveShouldSetResponder={[Function]}
onMoveShouldSetResponderCapture={[Function]}
onResponderEnd={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderReject={[Function]}
onResponderRelease={[Function]}
onResponderStart={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
style={
Object {
"backgroundColor": "transparent",
"flex": 1,
}
}
>
<View
collapsable={undefined}
style={
Object {
"flex": 1,
"zIndex": 0,
}
}
/>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hitSlop={undefined}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
pointerEvents="none"
style={
Object {
"backgroundColor": "#000",
"bottom": 0,
"left": 0,
"opacity": 0,
"position": "absolute",
"right": 0,
"top": 0,
"zIndex": 1000,
}
}
testID={undefined}
/>
<View
accessibilityViewIsModal={false}
collapsable={undefined}
style={
Object {
"backgroundColor": "white",
"bottom": 0,
"left": 0,
"position": "absolute",
"right": null,
"top": 0,
"transform": Array [
Object {
"translateX": -320,
},
],
"width": 320,
"zIndex": 1001,
}
}
>
<View
style={
Array [
Object {
"flex": 1,
},
undefined,
]
}
>
<RCTScrollView
DEPRECATED_sendUpdatedChildFrames={false}
alwaysBounceHorizontal={undefined}
alwaysBounceVertical={false}
onContentSizeChange={null}
onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]}
onResponderGrant={[Function]}
onResponderReject={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={undefined}
onResponderTerminationRequest={[Function]}
onScroll={[Function]}
onScrollBeginDrag={[Function]}
onScrollEndDrag={[Function]}
onScrollShouldSetResponder={[Function]}
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
onTouchCancel={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
scrollEventThrottle={undefined}
sendMomentumEvents={false}
style={
Array [
Object {
"flexDirection": "column",
"flexGrow": 1,
"flexShrink": 1,
"overflow": "scroll",
},
undefined,
]
}
>
<RCTScrollContentView
collapsable={false}
removeClippedSubviews={undefined}
style={
Array [
undefined,
undefined,
]
}
>
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
}
}
>
<View
style={
Array [
Object {
"paddingVertical": 4,
},
undefined,
]
}
>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
collapsable={undefined}
hasTVPreferredFocus={undefined}
hitSlop={undefined}
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
testID={undefined}
tvParallaxProperties={undefined}
>
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"backgroundColor": "rgba(0, 0, 0, .04)",
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 0,
}
}
>
<View
style={
Array [
Object {
"alignItems": "center",
"flexDirection": "row",
},
undefined,
]
}
>
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
style={
Array [
Object {
"fontWeight": "bold",
"margin": 16,
},
Object {
"color": "#2196f3",
},
undefined,
undefined,
]
}
>
Welcome anonymous
</Text>
</View>
</View>
</View>
</View>
</View>
</RCTScrollContentView>
</RCTScrollView>
</View>
</View>
</View>
`;

View File

@@ -1,369 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Nested navigators renders succesfully as direct child 1`] = `
<View
onLayout={[Function]}
style={
Array [
Object {
"flex": 1,
},
]
}
>
<View
onMoveShouldSetResponder={[Function]}
onMoveShouldSetResponderCapture={[Function]}
onResponderEnd={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderReject={[Function]}
onResponderRelease={[Function]}
onResponderStart={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
style={
Array [
Object {
"flex": 1,
"flexDirection": "column-reverse",
},
Object {
"backgroundColor": "#000",
},
]
}
>
<View
style={
Object {
"flex": 1,
}
}
>
<View
collapsable={undefined}
pointerEvents="auto"
style={
Object {
"backgroundColor": "#E9E9EF",
"bottom": 0,
"left": 0,
"marginTop": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"shadowColor": "black",
"shadowOffset": Object {
"height": 0,
"width": 0,
},
"shadowOpacity": 0.2,
"shadowRadius": 5,
"top": 0,
"transform": Array [
Object {
"translateX": 0,
},
Object {
"translateY": 0,
},
],
}
}
>
<View
onLayout={[Function]}
style={
Array [
Object {
"flex": 1,
},
]
}
>
<View
onMoveShouldSetResponder={[Function]}
onMoveShouldSetResponderCapture={[Function]}
onResponderEnd={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderReject={[Function]}
onResponderRelease={[Function]}
onResponderStart={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
style={
Array [
Object {
"flex": 1,
"flexDirection": "column-reverse",
},
Object {
"backgroundColor": "#000",
},
]
}
>
<View
style={
Object {
"flex": 1,
}
}
>
<View
collapsable={undefined}
pointerEvents="auto"
style={
Object {
"backgroundColor": "#E9E9EF",
"bottom": 0,
"left": 0,
"marginTop": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"shadowColor": "black",
"shadowOffset": Object {
"height": 0,
"width": 0,
},
"shadowOpacity": 0.2,
"shadowRadius": 5,
"top": 0,
"transform": Array [
Object {
"translateX": 0,
},
Object {
"translateY": 0,
},
],
}
}
/>
</View>
<View
onLayout={[Function]}
pointerEvents="box-none"
>
<View
collapsable={undefined}
style={
Object {
"transform": Array [
Object {
"translateX": 0,
},
],
}
}
>
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"backgroundColor": "#F7F7F7",
"borderBottomColor": "#A7A7AA",
"borderBottomWidth": 0.5,
"height": 64,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
}
}
>
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
style={
Object {
"flex": 1,
}
}
>
<View
style={
Object {
"bottom": 0,
"flexDirection": "row",
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<View
collapsable={undefined}
pointerEvents="box-none"
style={
Object {
"alignItems": "center",
"backgroundColor": "transparent",
"bottom": 0,
"flexDirection": "row",
"justifyContent": "center",
"left": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<Text
accessibilityTraits="header"
accessible={true}
allowFontScaling={true}
collapsable={undefined}
ellipsizeMode="tail"
numberOfLines={1}
onLayout={[Function]}
style={
Object {
"color": "rgba(0, 0, 0, .9)",
"fontSize": 17,
"fontWeight": "700",
"marginHorizontal": 16,
"textAlign": "center",
}
}
/>
</View>
</View>
</View>
</View>
</View>
</View>
</View>
</View>
</View>
</View>
<View
onLayout={[Function]}
pointerEvents="box-none"
>
<View
collapsable={undefined}
style={
Object {
"transform": Array [
Object {
"translateX": 0,
},
],
}
}
>
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"backgroundColor": "#F7F7F7",
"borderBottomColor": "#A7A7AA",
"borderBottomWidth": 0.5,
"height": 64,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
}
}
>
<View
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
style={
Object {
"flex": 1,
}
}
>
<View
style={
Object {
"bottom": 0,
"flexDirection": "row",
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<View
collapsable={undefined}
pointerEvents="box-none"
style={
Object {
"alignItems": "center",
"backgroundColor": "transparent",
"bottom": 0,
"flexDirection": "row",
"justifyContent": "center",
"left": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<Text
accessibilityTraits="header"
accessible={true}
allowFontScaling={true}
collapsable={undefined}
ellipsizeMode="tail"
numberOfLines={1}
onLayout={[Function]}
style={
Object {
"color": "rgba(0, 0, 0, .9)",
"fontSize": 17,
"fontWeight": "700",
"marginHorizontal": 16,
"textAlign": "center",
}
}
/>
</View>
</View>
</View>
</View>
</View>
</View>
</View>
</View>
`;
exports[`Nested navigators throw when trying to pass navigation prop 1`] = `"No \\"routes\\" found in navigation state. Did you try to pass the navigation prop of a React component to a Navigator child? See https://reactnavigation.org/docs/en/custom-navigators.html#navigator-navigation-prop"`;

View File

@@ -51,7 +51,6 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
"backgroundColor": "#E9E9EF",
"bottom": 0,
"left": 0,
"marginTop": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
@@ -76,127 +75,109 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
/>
</View>
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"backgroundColor": "red",
"borderBottomColor": "#A7A7AA",
"borderBottomWidth": 0.5,
"height": 64,
"opacity": 0.5,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
}
}
>
<View
collapsable={undefined}
style={
Object {
"transform": Array [
Object {
"translateX": 0,
},
],
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
style={
Object {
"flex": 1,
}
}
>
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"backgroundColor": "red",
"borderBottomColor": "#A7A7AA",
"borderBottomWidth": 0.5,
"height": 64,
"opacity": 0.5,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
"bottom": 0,
"flexDirection": "row",
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<View
collapsable={undefined}
pointerEvents="box-none"
style={
Object {
"alignItems": "center",
"backgroundColor": "transparent",
"bottom": 0,
"left": 0,
"flexDirection": "row",
"justifyContent": "center",
"left": 70,
"opacity": 1,
"position": "absolute",
"right": 70,
"top": 0,
}
}
>
<Text
accessibilityTraits="header"
accessible={true}
allowFontScaling={true}
collapsable={undefined}
ellipsizeMode="tail"
numberOfLines={1}
onLayout={[Function]}
style={
Object {
"color": "rgba(0, 0, 0, .9)",
"fontSize": 17,
"fontWeight": "700",
"marginHorizontal": 16,
"textAlign": "center",
}
}
>
Welcome anonymous
</Text>
</View>
<View
collapsable={undefined}
pointerEvents="box-none"
style={
Object {
"alignItems": "center",
"backgroundColor": "transparent",
"bottom": 0,
"flexDirection": "row",
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
style={
Object {
"flex": 1,
}
}
>
<View
style={
Object {
"bottom": 0,
"flexDirection": "row",
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<View
collapsable={undefined}
pointerEvents="box-none"
style={
Object {
"alignItems": "center",
"backgroundColor": "transparent",
"bottom": 0,
"flexDirection": "row",
"justifyContent": "center",
"left": 70,
"opacity": 1,
"position": "absolute",
"right": 70,
"top": 0,
}
}
>
<Text
accessibilityTraits="header"
accessible={true}
allowFontScaling={true}
collapsable={undefined}
ellipsizeMode="tail"
numberOfLines={1}
onLayout={[Function]}
style={
Object {
"color": "rgba(0, 0, 0, .9)",
"fontSize": 17,
"fontWeight": "700",
"marginHorizontal": 16,
"textAlign": "center",
}
}
>
Welcome anonymous
</Text>
</View>
<View
collapsable={undefined}
pointerEvents="box-none"
style={
Object {
"alignItems": "center",
"backgroundColor": "transparent",
"bottom": 0,
"flexDirection": "row",
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<View />
</View>
</View>
<View />
</View>
</View>
</View>
@@ -256,7 +237,6 @@ exports[`StackNavigator renders successfully 1`] = `
"backgroundColor": "#E9E9EF",
"bottom": 0,
"left": 0,
"marginTop": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
@@ -281,109 +261,91 @@ exports[`StackNavigator renders successfully 1`] = `
/>
</View>
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"backgroundColor": "red",
"borderBottomColor": "#A7A7AA",
"borderBottomWidth": 0.5,
"height": 64,
"opacity": 0.5,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
}
}
>
<View
collapsable={undefined}
style={
Object {
"transform": Array [
Object {
"translateX": 0,
},
],
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
style={
Object {
"flex": 1,
}
}
>
<View
collapsable={undefined}
onLayout={[Function]}
pointerEvents="box-none"
style={
Object {
"backgroundColor": "red",
"borderBottomColor": "#A7A7AA",
"borderBottomWidth": 0.5,
"height": 64,
"opacity": 0.5,
"paddingBottom": 0,
"paddingLeft": 0,
"paddingRight": 0,
"paddingTop": 20,
"bottom": 0,
"flexDirection": "row",
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<View
collapsable={undefined}
pointerEvents="box-none"
style={
Object {
"alignItems": "center",
"backgroundColor": "transparent",
"bottom": 0,
"flexDirection": "row",
"justifyContent": "center",
"left": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
<View
style={
Object {
"flex": 1,
}
}
>
<View
<Text
accessibilityTraits="header"
accessible={true}
allowFontScaling={true}
collapsable={undefined}
ellipsizeMode="tail"
numberOfLines={1}
onLayout={[Function]}
style={
Object {
"bottom": 0,
"flexDirection": "row",
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
"color": "rgba(0, 0, 0, .9)",
"fontSize": 17,
"fontWeight": "700",
"marginHorizontal": 16,
"textAlign": "center",
}
}
>
<View
collapsable={undefined}
pointerEvents="box-none"
style={
Object {
"alignItems": "center",
"backgroundColor": "transparent",
"bottom": 0,
"flexDirection": "row",
"justifyContent": "center",
"left": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
}
}
>
<Text
accessibilityTraits="header"
accessible={true}
allowFontScaling={true}
collapsable={undefined}
ellipsizeMode="tail"
numberOfLines={1}
onLayout={[Function]}
style={
Object {
"color": "rgba(0, 0, 0, .9)",
"fontSize": 17,
"fontWeight": "700",
"marginHorizontal": 16,
"textAlign": "center",
}
}
>
Welcome anonymous
</Text>
</View>
</View>
Welcome anonymous
</Text>
</View>
</View>
</View>

View File

@@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SwitchNavigator renders successfully 1`] = `<View />`;

View File

@@ -2,7 +2,12 @@
exports[`TabNavigator renders successfully 1`] = `
<View
collapsable={false}
loaded={
Array [
0,
]
}
onLayout={[Function]}
style={
Array [
Object {
@@ -16,65 +21,56 @@ exports[`TabNavigator renders successfully 1`] = `
}
>
<View
onLayout={[Function]}
collapsable={undefined}
onMoveShouldSetResponder={[Function]}
onMoveShouldSetResponderCapture={[Function]}
onResponderEnd={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderReject={[Function]}
onResponderRelease={[Function]}
onResponderStart={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
style={
Object {
"alignItems": "stretch",
"flex": 1,
"flexDirection": "row",
}
}
>
<View
collapsable={undefined}
onMoveShouldSetResponder={[Function]}
onMoveShouldSetResponderCapture={[Function]}
onResponderEnd={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderReject={[Function]}
onResponderRelease={[Function]}
onResponderStart={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
style={
Object {
"alignItems": "stretch",
"flex": 1,
"flexDirection": "row",
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
testID={undefined}
>
<View
collapsable={false}
removeClippedSubviews={false}
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
"flex": 1,
"overflow": "hidden",
}
}
testID={undefined}
>
<View
collapsable={false}
removeClippedSubviews={false}
style={
Object {
"flex": 1,
"overflow": "hidden",
}
}
>
<View
style={
Object {
"flex": 1,
}
}
/>
</View>
/>
</View>
</View>
</View>
@@ -141,15 +137,9 @@ exports[`TabNavigator renders successfully 1`] = `
>
<View
style={
Array [
Object {
"height": 29,
},
false,
Object {
"flex": 1,
},
]
Object {
"flexGrow": 1,
}
}
>
<View
@@ -160,7 +150,6 @@ exports[`TabNavigator renders successfully 1`] = `
"alignSelf": "center",
"height": "100%",
"justifyContent": "center",
"minWidth": 30,
"opacity": 1,
"position": "absolute",
"width": "100%",
@@ -175,7 +164,6 @@ exports[`TabNavigator renders successfully 1`] = `
"alignSelf": "center",
"height": "100%",
"justifyContent": "center",
"minWidth": 30,
"opacity": 0,
"position": "absolute",
"width": "100%",

View File

@@ -1,9 +0,0 @@
import createNavigationContainer from '../createNavigationContainer';
import createStackNavigator from './createStackNavigator';
const StackNavigator = (routeConfigs, config = {}) => {
const navigator = createStackNavigator(routeConfigs, config);
return createNavigationContainer(navigator);
};
export default StackNavigator;

View File

@@ -1,9 +0,0 @@
import createNavigationContainer from '../createNavigationContainer';
import createSwitchNavigator from './createSwitchNavigator';
const SwitchNavigator = (routeConfigs, config = {}) => {
const navigator = createSwitchNavigator(routeConfigs, config);
return createNavigationContainer(navigator);
};
export default SwitchNavigator;

View File

@@ -1,55 +0,0 @@
import React from 'react';
import { TextInput } from 'react-native';
export default Navigator =>
class KeyboardAwareNavigator extends React.Component {
static router = Navigator.router;
_previouslyFocusedTextInput = null;
render() {
return (
<Navigator
{...this.props}
onGestureBegin={this._handleGestureBegin}
onGestureCanceled={this._handleGestureCanceled}
onGestureFinish={this._handleGestureFinish}
onTransitionStart={this._handleTransitionStart}
/>
);
}
_handleGestureBegin = () => {
this._previouslyFocusedTextInput = TextInput.State.currentlyFocusedField();
if (this._previouslyFocusedTextInput) {
TextInput.State.blurTextInput(this._previouslyFocusedTextInput);
}
this.props.onGestureBegin && this.props.onGestureBegin();
};
_handleGestureCanceled = () => {
if (this._previouslyFocusedTextInput) {
TextInput.State.focusTextInput(this._previouslyFocusedTextInput);
}
this.props.onGestureCanceled && this.props.onGestureCanceled();
};
_handleGestureFinish = () => {
this._previouslyFocusedTextInput = null;
this.props.onGestureFinish && this.props.onGestureFinish();
};
_handleTransitionStart = (transitionProps, prevTransitionProps) => {
// TODO: We should not even have received the transition start event
// in the case where the index did not change, I believe. We
// should revisit this after 2.0 release.
if (transitionProps.index !== prevTransitionProps.index) {
const currentField = TextInput.State.currentlyFocusedField();
if (currentField) {
TextInput.State.blurTextInput(currentField);
}
}
this.props.onTransitionStart &&
this.props.onTransitionStart(transitionProps, prevTransitionProps);
};
};

View File

@@ -1,44 +1,60 @@
import React from 'react';
import { polyfill } from 'react-lifecycles-compat';
import getChildEventSubscriber from '../getChildEventSubscriber';
import addNavigationHelpers from '../addNavigationHelpers';
function createNavigator(NavigatorView, router, navigationConfig) {
class Navigator extends React.Component {
static router = router;
static navigationOptions = null;
state = {
descriptors: {},
screenProps: this.props.screenProps,
childEventSubscribers = {};
// Cleanup subscriptions for routes that no longer exist
componentDidUpdate() {
const activeKeys = this.props.navigation.state.routes.map(r => r.key);
Object.keys(this.childEventSubscribers).forEach(key => {
if (!activeKeys.includes(key)) {
this.childEventSubscribers[key].removeAll();
delete this.childEventSubscribers[key];
}
});
}
// Remove all subscriptions
componentWillUnmount() {
Object.values(this.childEventSubscribers).map(s => s.removeAll());
}
_isRouteFocused = route => () => {
const { state } = this.props.navigation;
const focusedRoute = state.routes[state.index];
return route === focusedRoute;
};
static getDerivedStateFromProps(nextProps, prevState) {
const prevDescriptors = prevState.descriptors;
const { navigation, screenProps } = nextProps;
render() {
const { navigation, screenProps } = this.props;
const { dispatch, state, addListener } = navigation;
const { routes } = state;
if (typeof routes === 'undefined') {
throw new TypeError(
'No "routes" found in navigation state. Did you try to pass the navigation prop of a React component to a Navigator child? See https://reactnavigation.org/docs/en/custom-navigators.html#navigator-navigation-prop'
);
}
const descriptors = { ...prevState.descriptors };
const descriptors = {};
routes.forEach(route => {
if (
prevDescriptors &&
prevDescriptors[route.key] &&
route === prevDescriptors[route.key].state &&
screenProps === prevState.screenProps
) {
descriptors[route.key] = prevDescriptors[route.key];
return;
}
const getComponent = () =>
router.getComponentForRouteName(route.routeName);
const childNavigation = navigation.getChildNavigation(route.key);
if (!this.childEventSubscribers[route.key]) {
this.childEventSubscribers[route.key] = getChildEventSubscriber(
addListener,
route.key
);
}
const childNavigation = addNavigationHelpers({
dispatch,
state: route,
addListener: this.childEventSubscribers[route.key].addListener,
isFocused: this._isRouteFocused.bind(this, route),
});
const options = router.getScreenOptions(childNavigation, screenProps);
descriptors[route.key] = {
key: route.key,
@@ -49,23 +65,17 @@ function createNavigator(NavigatorView, router, navigationConfig) {
};
});
return { descriptors, screenProps };
}
render() {
return (
<NavigatorView
{...this.props}
screenProps={this.state.screenProps}
navigation={this.props.navigation}
screenProps={screenProps}
navigation={navigation}
navigationConfig={navigationConfig}
descriptors={this.state.descriptors}
descriptors={descriptors}
/>
);
}
}
return polyfill(Navigator);
return Navigator;
}
export default createNavigator;

View File

@@ -1,37 +1,31 @@
import createKeyboardAwareNavigator from './createKeyboardAwareNavigator';
import * as React from 'react';
import createNavigationContainer from '../createNavigationContainer';
import createNavigator from './createNavigator';
import StackView from '../views/StackView/StackView';
import StackView from '../views/StackView/StackView2';
import StackRouter from '../routers/StackRouter';
function createStackNavigator(routeConfigMap, stackConfig = {}) {
const {
initialRouteKey,
initialRouteName,
initialRouteParams,
paths,
navigationOptions,
disableKeyboardHandling,
getCustomActionCreators,
} = stackConfig;
const stackRouterConfig = {
initialRouteKey,
initialRouteName,
initialRouteParams,
paths,
navigationOptions,
getCustomActionCreators,
};
const router = StackRouter(routeConfigMap, stackRouterConfig);
// Create a navigator with StackView as the view
let Navigator = createNavigator(StackView, router, stackConfig);
if (!disableKeyboardHandling) {
Navigator = createKeyboardAwareNavigator(Navigator);
}
const Navigator = createNavigator(StackView, router, stackConfig);
return Navigator;
// HOC to provide the navigation prop for the top-level navigator (when the prop is missing)
return createNavigationContainer(Navigator);
}
export default createStackNavigator;

View File

@@ -1,12 +0,0 @@
import React from 'react';
import createNavigator from '../navigators/createNavigator';
import SwitchRouter from '../routers/SwitchRouter';
import SwitchView from '../views/SwitchView/SwitchView';
function createSwitchNavigator(routeConfigMap, switchConfig = {}) {
const router = SwitchRouter(routeConfigMap, switchConfig);
const Navigator = createNavigator(SwitchView, router, switchConfig);
return Navigator;
}
export default createSwitchNavigator;

View File

@@ -0,0 +1,64 @@
import React from 'react';
import { Platform } from 'react-native';
import createNavigator from './createNavigator';
import createNavigationContainer from '../createNavigationContainer';
import TabRouter from '../routers/TabRouter';
import TabView from '../views/TabView/TabView';
import TabBarTop from '../views/TabView/TabBarTop';
import TabBarBottom from '../views/TabView/TabBarBottom';
const TabNavigator = (routeConfigs, config = {}) => {
// Use the look native to the platform by default
const tabsConfig = { ...TabNavigator.Presets.Default, ...config };
const router = TabRouter(routeConfigs, tabsConfig);
const navigator = createNavigator(TabView, router, tabsConfig);
return createNavigationContainer(navigator);
};
const Presets = {
iOSBottomTabs: {
tabBarComponent: TabBarBottom,
tabBarPosition: 'bottom',
swipeEnabled: false,
animationEnabled: false,
initialLayout: undefined,
},
AndroidTopTabs: {
tabBarComponent: TabBarTop,
tabBarPosition: 'top',
swipeEnabled: true,
animationEnabled: true,
initialLayout: undefined,
},
};
/**
* Use these to get Android-style top tabs even on iOS or vice versa.
*
* Example:
* ```
* const HomeScreenTabNavigator = TabNavigator({
* Chat: {
* screen: ChatScreen,
* },
* ...
* }, {
* ...TabNavigator.Presets.AndroidTopTabs,
* tabBarOptions: {
* ...
* },
* });
*```
*/
TabNavigator.Presets = {
iOSBottomTabs: Presets.iOSBottomTabs,
AndroidTopTabs: Presets.AndroidTopTabs,
Default:
Platform.OS === 'ios' ? Presets.iOSBottomTabs : Presets.AndroidTopTabs,
};
export default TabNavigator;

View File

@@ -8,71 +8,25 @@ module.exports = {
get StateUtils() {
return require('./StateUtils').default;
},
get getNavigation() {
return require('./getNavigation').default;
get addNavigationHelpers() {
return require('./addNavigationHelpers').default;
},
get NavigationActions() {
return require('./NavigationActions').default;
},
// Navigators
get createNavigator() {
return require('./navigators/createNavigator').default;
},
get createStackNavigator() {
return require('./navigators/createContainedStackNavigator').default;
},
get StackNavigator() {
console.warn(
'The StackNavigator function name is deprecated, please use createStackNavigator instead'
);
return require('./navigators/createContainedStackNavigator').default;
},
get createSwitchNavigator() {
return require('./navigators/createContainedSwitchNavigator').default;
},
get SwitchNavigator() {
console.warn(
'The SwitchNavigator function name is deprecated, please use createSwitchNavigator instead'
);
return require('./navigators/createContainedSwitchNavigator').default;
},
get createDrawerNavigator() {
return require('react-navigation-drawer').createDrawerNavigator;
},
get DrawerNavigator() {
console.warn(
'The DrawerNavigator function name is deprecated, please use createDrawerNavigator instead'
);
return require('react-navigation-drawer').createDrawerNavigator;
},
get createTabNavigator() {
console.warn(
'createTabNavigator is deprecated. Please use the createBottomTabNavigator or createMaterialTopTabNavigator instead.'
);
return require('react-navigation-deprecated-tab-navigator')
.createTabNavigator;
return require('./navigators/createStackNavigator').default;
},
get TabNavigator() {
console.warn(
'TabNavigator is deprecated. Please use the createBottomTabNavigator or createMaterialTopTabNavigator instead.'
);
return require('react-navigation-deprecated-tab-navigator')
.createTabNavigator;
return require('./navigators/createTabNavigator').default;
},
get createBottomTabNavigator() {
return require('react-navigation-tabs').createBottomTabNavigator;
},
get createMaterialTopTabNavigator() {
return require('react-navigation-tabs').createMaterialTopTabNavigator;
},
// Actions
get NavigationActions() {
return require('./NavigationActions').default;
},
get StackActions() {
return require('./routers/StackActions').default;
},
get DrawerActions() {
return require('react-navigation-drawer').DrawerActions;
get DrawerNavigator() {
return require('./navigators/DrawerNavigator').default;
},
// Routers
@@ -82,12 +36,6 @@ module.exports = {
get TabRouter() {
return require('./routers/TabRouter').default;
},
get DrawerRouter() {
return require('react-navigation-drawer').DrawerRouter;
},
get SwitchRouter() {
return require('./routers/SwitchRouter').default;
},
// Views
get Transitioner() {
@@ -102,12 +50,6 @@ module.exports = {
get SafeAreaView() {
return require('react-native-safe-area-view').default;
},
get SceneView() {
return require('./views/SceneView').default;
},
get ResourceSavingSceneView() {
return require('./views/ResourceSavingSceneView').default;
},
// Header
get Header() {
@@ -122,38 +64,21 @@ module.exports = {
// DrawerView
get DrawerView() {
return require('react-navigation-drawer').DrawerView;
return require('./views/Drawer/DrawerView').default;
},
get DrawerItems() {
return require('react-navigation-drawer').DrawerNavigatorItems;
},
get DrawerSidebar() {
return require('react-navigation-drawer').DrawerSidebar;
return require('./views/Drawer/DrawerNavigatorItems').default;
},
// TabView
get TabView() {
console.warn(
'TabView is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
);
return require('react-navigation-deprecated-tab-navigator').TabView;
return require('./views/TabView/TabView').default;
},
get TabBarTop() {
console.warn(
'TabBarTop is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
);
return require('react-navigation-deprecated-tab-navigator').TabBarTop;
return require('./views/TabView/TabBarTop').default;
},
get TabBarBottom() {
console.warn(
'TabBarBottom is deprecated. Please use the react-navigation-tabs package instead: https://github.com/react-navigation/react-navigation-tabs'
);
return require('react-navigation-deprecated-tab-navigator').TabBarBottom;
},
// SwitchView
get SwitchView() {
return require('./views/SwitchView/SwitchView').default;
return require('./views/TabView/TabBarBottom').default;
},
// HOCs
@@ -163,7 +88,4 @@ module.exports = {
get withNavigationFocus() {
return require('./views/withNavigationFocus').default;
},
get withOrientation() {
return require('./views/withOrientation').default;
},
};

View File

@@ -8,23 +8,18 @@ module.exports = {
get StateUtils() {
return require('./StateUtils').default;
},
get addNavigationHelpers() {
return require('./addNavigationHelpers').default;
},
get NavigationActions() {
return require('./NavigationActions').default;
},
// Navigators
get createNavigator() {
return require('./navigators/createNavigator').default;
},
// Actions
get NavigationActions() {
return require('./NavigationActions').default;
},
get StackActions() {
return require('./routers/StackActions').default;
},
get DrawerActions() {
return require('./routers/DrawerActions').default;
},
// Routers
get StackRouter() {
return require('./routers/StackRouter').default;
@@ -32,9 +27,6 @@ module.exports = {
get TabRouter() {
return require('./routers/TabRouter').default;
},
get SwitchRouter() {
return require('./routers/SwitchRouter').default;
},
// HOCs
get withNavigation() {

View File

@@ -0,0 +1,55 @@
import invariant from '../utils/invariant';
import TabRouter from './TabRouter';
import NavigationActions from '../NavigationActions';
export default (routeConfigs, config = {}) => {
const tabRouter = TabRouter(routeConfigs, config);
return {
...tabRouter,
getStateForAction(action, lastState) {
const state = lastState || {
...tabRouter.getStateForAction(action, undefined),
isDrawerOpen: false,
};
// Handle explicit drawer actions
if (
state.isDrawerOpen &&
action.type === NavigationActions.CLOSE_DRAWER
) {
return {
...state,
isDrawerOpen: false,
};
}
if (
!state.isDrawerOpen &&
action.type === NavigationActions.OPEN_DRAWER
) {
return {
...state,
isDrawerOpen: true,
};
}
if (action.type === NavigationActions.TOGGLE_DRAWER) {
return {
...state,
isDrawerOpen: !state.isDrawerOpen,
};
}
// Fall back on tab router for screen switching logic
const tabState = tabRouter.getStateForAction(action, state);
if (tabState !== null && tabState !== state) {
// If the tabs have changed, make sure to close the drawer
return {
...tabState,
isDrawerOpen: false,
};
}
return state;
},
};
};

View File

@@ -1,52 +0,0 @@
const POP = 'Navigation/POP';
const POP_TO_TOP = 'Navigation/POP_TO_TOP';
const PUSH = 'Navigation/PUSH';
const RESET = 'Navigation/RESET';
const REPLACE = 'Navigation/REPLACE';
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
const pop = payload => ({
type: POP,
...payload,
});
const popToTop = payload => ({
type: POP_TO_TOP,
...payload,
});
const push = payload => ({
type: PUSH,
...payload,
});
const reset = payload => ({
type: RESET,
...payload,
});
const replace = payload => ({
type: REPLACE,
...payload,
});
const completeTransition = payload => ({
type: COMPLETE_TRANSITION,
...payload,
});
export default {
POP,
POP_TO_TOP,
PUSH,
RESET,
REPLACE,
COMPLETE_TRANSITION,
pop,
popToTop,
push,
reset,
replace,
completeTransition,
};

View File

@@ -1,14 +1,12 @@
import pathToRegexp from 'path-to-regexp';
import NavigationActions from '../NavigationActions';
import StackActions from './StackActions';
import createConfigGetter from './createConfigGetter';
import getScreenForRouteName from './getScreenForRouteName';
import StateUtils from '../StateUtils';
import validateRouteConfigMap from './validateRouteConfigMap';
import invariant from '../utils/invariant';
import { generateKey } from './KeyGenerator';
import getNavigationActionCreators from './getNavigationActionCreators';
function isEmpty(obj) {
if (!obj) return true;
@@ -21,16 +19,10 @@ function isEmpty(obj) {
function behavesLikePushAction(action) {
return (
action.type === NavigationActions.NAVIGATE ||
action.type === StackActions.PUSH
action.type === NavigationActions.PUSH
);
}
const defaultActionCreators = (route, navStateKey) => ({});
function isResetToRootStack(action) {
return action.type === StackActions.RESET && action.key === null;
}
export default (routeConfigs, stackConfig = {}) => {
// Fail fast on invalid route definitions
validateRouteConfigMap(routeConfigs);
@@ -51,8 +43,6 @@ export default (routeConfigs, stackConfig = {}) => {
});
const { initialRouteParams } = stackConfig;
const getCustomActionCreators =
stackConfig.getCustomActionCreators || defaultActionCreators;
const initialRouteName = stackConfig.initialRouteName || routeNames[0];
@@ -75,7 +65,7 @@ export default (routeConfigs, stackConfig = {}) => {
}
return {
key: 'StackRouterRoot',
isTransitioning: false,
transitioningFromKey: null,
index: 0,
routes: [
{
@@ -110,7 +100,7 @@ export default (routeConfigs, stackConfig = {}) => {
};
return {
key: 'StackRouterRoot',
isTransitioning: false,
transitioningFromKey: false,
index: 0,
routes: [route],
};
@@ -146,11 +136,9 @@ export default (routeConfigs, stackConfig = {}) => {
});
paths = Object.entries(pathsByRouteNames);
paths.sort((a, b) => b[1].priority - a[1].priority);
paths.sort((a: [string, *], b: [string, *]) => b[1].priority - a[1].priority);
return {
childRouters,
getComponentForState(state) {
const activeChildRoute = state.routes[state.index];
const { routeName } = activeChildRoute;
@@ -164,75 +152,16 @@ export default (routeConfigs, stackConfig = {}) => {
return getScreenForRouteName(routeConfigs, routeName);
},
getActionCreators(route, navStateKey) {
return {
...getNavigationActionCreators(route),
...getCustomActionCreators(route, navStateKey),
pop: (n, params) =>
StackActions.pop({
n,
...params,
}),
popToTop: params => StackActions.popToTop(params),
push: (routeName, params, action) =>
StackActions.push({
routeName,
params,
action,
}),
replace: (replaceWith, params, action, newKey) => {
if (typeof replaceWith === 'string') {
return StackActions.replace({
routeName: replaceWith,
params,
action,
key: route.key,
newKey,
});
}
invariant(
typeof replaceWith === 'object',
'Must replaceWith an object or a string'
);
invariant(
params == null,
'Params must not be provided to .replace() when specifying an object'
);
invariant(
action == null,
'Child action must not be provided to .replace() when specifying an object'
);
invariant(
newKey == null,
'Child action must not be provided to .replace() when specifying an object'
);
return StackActions.replace(replaceWith);
},
reset: (actions, index) =>
StackActions.reset({
actions,
index: index == null ? actions.length - 1 : index,
key: navStateKey,
}),
dismiss: () =>
NavigationActions.back({
key: navStateKey,
}),
};
},
getStateForAction(action, state) {
// Set up the initial state if needed
if (!state) {
return getInitialState(action);
}
const lastRouteKey = state.routes[state.index].key;
// Check if the focused child scene wants to handle the action, as long as
// it is not a reset to the root stack
if (
!isResetToRootStack(action) &&
action.type !== NavigationActions.NAVIGATE
) {
if (action.type !== NavigationActions.RESET || action.key !== null) {
const keyIndex = action.key
? StateUtils.indexOf(state, action.key)
: -1;
@@ -252,38 +181,6 @@ export default (routeConfigs, stackConfig = {}) => {
return StateUtils.replaceAt(state, childRoute.key, route);
}
}
} else if (action.type === NavigationActions.NAVIGATE) {
// Traverse routes from the top of the stack to the bottom, so the
// active route has the first opportunity, then the one before it, etc.
for (let childRoute of state.routes.slice().reverse()) {
let childRouter = childRouters[childRoute.routeName];
let childAction =
action.routeName === childRoute.routeName && action.action
? action.action
: action;
if (childRouter) {
const nextRouteState = childRouter.getStateForAction(
childAction,
childRoute
);
if (nextRouteState === null || nextRouteState !== childRoute) {
const newState = StateUtils.replaceAndPrune(
state,
nextRouteState ? nextRouteState.key : childRoute.key,
nextRouteState ? nextRouteState : childRoute
);
return {
...newState,
isTransitioning:
state.index !== newState.index
? action.immediate !== true
: state.isTransitioning,
};
}
}
}
}
// Handle explicit push navigation action. This must happen after the
@@ -296,50 +193,46 @@ export default (routeConfigs, stackConfig = {}) => {
let route;
invariant(
action.type !== StackActions.PUSH || action.key == null,
action.type !== NavigationActions.PUSH || action.key == null,
'StackRouter does not support key on the push action'
);
// Before pushing a new route we first try to find one in the existing route stack
// More information on this: https://github.com/react-navigation/rfcs/blob/master/text/0004-less-pushy-navigate.md
const lastRouteIndex = state.routes.findIndex(r => {
if (action.key) {
return r.key === action.key;
} else {
return r.routeName === action.routeName;
}
});
// With the navigate action, the key may be provided for pushing, or to navigate back to the key
if (action.key) {
const lastRouteIndex = state.routes.findIndex(
r => r.key === action.key
);
if (lastRouteIndex !== -1) {
// If index is unchanged and params are not being set, leave state identity intact
if (state.index === lastRouteIndex && !action.params) {
return state;
}
if (action.type !== StackActions.PUSH && lastRouteIndex !== -1) {
// If index is unchanged and params are not being set, leave state identity intact
if (state.index === lastRouteIndex && !action.params) {
return null;
}
// Remove the now unused routes at the tail of the routes array
const routes = state.routes.slice(0, lastRouteIndex + 1);
// Remove the now unused routes at the tail of the routes array
const routes = state.routes.slice(0, lastRouteIndex + 1);
// Apply params if provided, otherwise leave route identity intact
if (action.params) {
const route = state.routes[lastRouteIndex];
routes[lastRouteIndex] = {
...route,
params: {
...route.params,
...action.params,
},
// Apply params if provided, otherwise leave route identity intact
if (action.params) {
const route = state.routes.find(r => r.key === action.key);
routes[lastRouteIndex] = {
...route,
params: {
...route.params,
...action.params,
},
};
}
// Return state with new index. Change transitioningFromKey only if index has changed
return {
...state,
transitioningFromKey:
state.index !== lastRouteIndex
? action.immediate !== true ? lastRouteKey : null
: null,
index: lastRouteIndex,
routes,
};
}
// Return state with new index. Change isTransitioning only if index has changed
return {
...state,
isTransitioning:
state.index !== lastRouteIndex
? action.immediate !== true
: state.isTransitioning,
index: lastRouteIndex,
routes,
};
}
if (childRouter) {
@@ -361,14 +254,18 @@ export default (routeConfigs, stackConfig = {}) => {
}
return {
...StateUtils.push(state, route),
isTransitioning: action.immediate !== true,
transitioningFromKey: action.immediate !== true ? lastRouteKey : null,
};
} else if (
action.type === StackActions.PUSH &&
action.type === NavigationActions.PUSH &&
childRouters[action.routeName] === undefined
) {
// Return the state identity to bubble the action up
return state;
// If we've made it this far with a push action, we return the
// state with a new identity to prevent the action from bubbling
// back up.
return {
...state,
};
}
// Handle navigation to other child routers that are not yet pushed
@@ -401,17 +298,14 @@ export default (routeConfigs, stackConfig = {}) => {
routeName: childRouterName,
key: action.key || generateKey(),
};
return {
...StateUtils.push(state, route),
isTransitioning: action.immediate !== true,
};
return StateUtils.push(state, route);
}
}
}
}
// Handle pop-to-top behavior. Make sure this happens after children have had a chance to handle the action, so that the inner stack pops to top first.
if (action.type === StackActions.POP_TO_TOP) {
if (action.type === NavigationActions.POP_TO_TOP) {
// Refuse to handle pop to top if a key is given that doesn't correspond
// to this router
if (action.key && state.key !== action.key) {
@@ -420,10 +314,14 @@ export default (routeConfigs, stackConfig = {}) => {
// If we're already at the top, then we return the state with a new
// identity so that the action is handled by this router.
if (state.index > 0) {
if (state.index === 0) {
return {
...state,
isTransitioning: action.immediate !== true,
};
} else {
return {
...state,
lastRouteKey: action.immediate !== true ? lastRouteKey : null,
index: 0,
routes: [state.routes[0]],
};
@@ -432,7 +330,7 @@ export default (routeConfigs, stackConfig = {}) => {
}
// Handle replace action
if (action.type === StackActions.REPLACE) {
if (action.type === NavigationActions.REPLACE) {
const routeIndex = state.routes.findIndex(r => r.key === action.key);
// Only replace if the key matches one of our routes
if (routeIndex !== -1) {
@@ -458,13 +356,13 @@ export default (routeConfigs, stackConfig = {}) => {
// Update transitioning state
if (
action.type === StackActions.COMPLETE_TRANSITION &&
action.type === NavigationActions.COMPLETE_TRANSITION &&
(action.key == null || action.key === state.key) &&
state.isTransitioning
state.transitioningFromKey
) {
return {
...state,
isTransitioning: false,
transitioningFromKey: null,
};
}
@@ -488,7 +386,7 @@ export default (routeConfigs, stackConfig = {}) => {
}
}
if (action.type === StackActions.RESET) {
if (action.type === NavigationActions.RESET) {
// Only handle reset actions that are unspecified or match this state key
if (action.key != null && action.key != state.key) {
// Deliberately use != instead of !== so we can match null with
@@ -525,11 +423,11 @@ export default (routeConfigs, stackConfig = {}) => {
if (
action.type === NavigationActions.BACK ||
action.type === StackActions.POP
action.type === NavigationActions.POP
) {
const { key, n, immediate } = action;
let backRouteIndex = state.index;
if (action.type === StackActions.POP && n != null) {
if (action.type === NavigationActions.POP && n != null) {
// determine the index to go back *from*. In this case, n=1 means to go
// back from state.index, as if it were a normal "BACK" action
backRouteIndex = Math.max(1, state.index - n + 1);
@@ -543,11 +441,17 @@ export default (routeConfigs, stackConfig = {}) => {
...state,
routes: state.routes.slice(0, backRouteIndex),
index: backRouteIndex - 1,
isTransitioning: immediate !== true,
transitioningFromKey: immediate !== true ? lastRouteKey : null,
};
} else if (
backRouteIndex === 0 &&
action.type === NavigationActions.POP
) {
return {
...state,
};
}
}
return state;
},
@@ -578,7 +482,6 @@ export default (routeConfigs, stackConfig = {}) => {
if (!pathToResolve) {
return NavigationActions.navigate({
routeName: initialRouteName,
params: inputParams,
});
}
@@ -649,7 +552,7 @@ export default (routeConfigs, stackConfig = {}) => {
if (key.asterisk || !key) {
return result;
}
const nextResult = result || inputParams || {};
const nextResult = result || {};
const paramName = key.name;
let decodedMatchResult;

View File

@@ -1,390 +0,0 @@
import invariant from '../utils/invariant';
import getScreenForRouteName from './getScreenForRouteName';
import createConfigGetter from './createConfigGetter';
import NavigationActions from '../NavigationActions';
import StackActions from './StackActions';
import validateRouteConfigMap from './validateRouteConfigMap';
import getNavigationActionCreators from './getNavigationActionCreators';
const defaultActionCreators = (route, navStateKey) => ({});
function childrenUpdateWithoutSwitchingIndex(actionType) {
return [
NavigationActions.SET_PARAMS,
// Todo: make SwitchRouter not depend on StackActions..
StackActions.COMPLETE_TRANSITION,
].includes(actionType);
}
export default (routeConfigs, config = {}) => {
// Fail fast on invalid route definitions
validateRouteConfigMap(routeConfigs);
const order = config.order || Object.keys(routeConfigs);
const paths = config.paths || {};
const getCustomActionCreators =
config.getCustomActionCreators || defaultActionCreators;
const initialRouteParams = config.initialRouteParams;
const initialRouteName = config.initialRouteName || order[0];
const backBehavior = config.backBehavior || 'none';
const shouldBackNavigateToInitialRoute = backBehavior === 'initialRoute';
const resetOnBlur = config.hasOwnProperty('resetOnBlur')
? config.resetOnBlur
: true;
const initialRouteIndex = order.indexOf(initialRouteName);
const childRouters = {};
order.forEach(routeName => {
const routeConfig = routeConfigs[routeName];
if (!paths[routeName]) {
paths[routeName] =
typeof routeConfig.path === 'string' ? routeConfig.path : routeName;
}
childRouters[routeName] = null;
const screen = getScreenForRouteName(routeConfigs, routeName);
if (screen.router) {
childRouters[routeName] = screen.router;
}
});
if (initialRouteIndex === -1) {
throw new Error(
`Invalid initialRouteName '${initialRouteName}'.` +
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
);
}
function resetChildRoute(routeName) {
const params =
routeName === initialRouteName ? initialRouteParams : undefined;
const childRouter = childRouters[routeName];
if (childRouter) {
const childAction = NavigationActions.init();
return {
...childRouter.getStateForAction(childAction),
key: routeName,
routeName,
params,
};
}
return {
key: routeName,
routeName,
params,
};
}
function getNextState(prevState, possibleNextState) {
if (!prevState) {
return possibleNextState;
}
let nextState;
if (prevState.index !== possibleNextState.index && resetOnBlur) {
const prevRouteName = prevState.routes[prevState.index].routeName;
const nextRoutes = [...possibleNextState.routes];
nextRoutes[prevState.index] = resetChildRoute(prevRouteName);
return {
...possibleNextState,
routes: nextRoutes,
};
} else {
nextState = possibleNextState;
}
return nextState;
}
function getInitialState() {
const routes = order.map(resetChildRoute);
return {
routes,
index: initialRouteIndex,
isTransitioning: false,
};
}
return {
childRouters,
getActionCreators(route, stateKey) {
return {
...getNavigationActionCreators(route),
...getCustomActionCreators(route, stateKey),
};
},
getStateForAction(action, inputState) {
let prevState = inputState ? { ...inputState } : inputState;
let state = inputState || getInitialState();
let activeChildIndex = state.index;
if (action.type === NavigationActions.INIT) {
// NOTE(brentvatne): this seems weird... why are we merging these
// params into child routes?
// ---------------------------------------------------------------
// Merge any params from the action into all the child routes
const { params } = action;
if (params) {
state.routes = state.routes.map(route => ({
...route,
params: {
...route.params,
...params,
...(route.routeName === initialRouteName
? initialRouteParams
: null),
},
}));
}
}
// Let the current child handle it
const activeChildLastState = state.routes[state.index];
const activeChildRouter = childRouters[order[state.index]];
if (activeChildRouter) {
const activeChildState = activeChildRouter.getStateForAction(
action,
activeChildLastState
);
if (!activeChildState && inputState) {
return null;
}
if (activeChildState && activeChildState !== activeChildLastState) {
const routes = [...state.routes];
routes[state.index] = activeChildState;
return getNextState(prevState, {
...state,
routes,
});
}
}
// Handle tab changing. Do this after letting the current tab try to
// handle the action, to allow inner children to change first
const isBackEligible =
action.key == null || action.key === activeChildLastState.key;
if (action.type === NavigationActions.BACK) {
if (isBackEligible && shouldBackNavigateToInitialRoute) {
activeChildIndex = initialRouteIndex;
} else {
return state;
}
}
let didNavigate = false;
if (action.type === NavigationActions.NAVIGATE) {
didNavigate = !!order.find((childId, i) => {
if (childId === action.routeName) {
activeChildIndex = i;
return true;
}
return false;
});
if (didNavigate) {
const childState = state.routes[activeChildIndex];
const childRouter = childRouters[action.routeName];
let newChildState;
if (action.action) {
newChildState = childRouter
? childRouter.getStateForAction(action.action, childState)
: null;
} else if (!action.action && !childRouter && action.params) {
newChildState = {
...childState,
params: {
...(childState.params || {}),
...action.params,
},
};
}
if (newChildState && newChildState !== childState) {
const routes = [...state.routes];
routes[activeChildIndex] = newChildState;
return getNextState(prevState, {
...state,
routes,
index: activeChildIndex,
});
} else if (
!newChildState &&
state.index === activeChildIndex &&
prevState
) {
return null;
}
}
}
if (action.type === NavigationActions.SET_PARAMS) {
const key = action.key;
const lastRoute = state.routes.find(route => route.key === key);
if (lastRoute) {
const params = {
...lastRoute.params,
...action.params,
};
const routes = [...state.routes];
routes[state.routes.indexOf(lastRoute)] = {
...lastRoute,
params,
};
return getNextState(prevState, {
...state,
routes,
});
}
}
if (activeChildIndex !== state.index) {
return getNextState(prevState, {
...state,
index: activeChildIndex,
});
} else if (didNavigate && !inputState) {
return state;
} else if (didNavigate) {
return { ...state };
}
// Let other children handle it and switch to the first child that returns a new state
let index = state.index;
let routes = state.routes;
order.find((childId, i) => {
const childRouter = childRouters[childId];
if (i === index) {
return false;
}
let childState = routes[i];
if (childRouter) {
childState = childRouter.getStateForAction(action, childState);
}
if (!childState) {
index = i;
return true;
}
if (childState !== routes[i]) {
routes = [...routes];
routes[i] = childState;
index = i;
return true;
}
return false;
});
// Nested routers can be updated after switching children with actions such as SET_PARAMS
// and COMPLETE_TRANSITION.
// NOTE: This may be problematic with custom routers because we whitelist the actions
// that can be handled by child routers without automatically changing index.
if (childrenUpdateWithoutSwitchingIndex(action.type)) {
index = state.index;
}
if (index !== state.index || routes !== state.routes) {
return getNextState(prevState, {
...state,
index,
routes,
});
}
return state;
},
getComponentForState(state) {
const routeName = state.routes[state.index].routeName;
invariant(
routeName,
`There is no route defined for index ${state.index}. Check that
that you passed in a navigation state with a valid tab/screen index.`
);
const childRouter = childRouters[routeName];
if (childRouter) {
return childRouter.getComponentForState(state.routes[state.index]);
}
return getScreenForRouteName(routeConfigs, routeName);
},
getComponentForRouteName(routeName) {
return getScreenForRouteName(routeConfigs, routeName);
},
getPathAndParamsForState(state) {
const route = state.routes[state.index];
const routeName = order[state.index];
const subPath = paths[routeName];
const screen = getScreenForRouteName(routeConfigs, routeName);
let path = subPath;
let params = route.params;
if (screen && screen.router) {
const stateRoute = route;
// If it has a router it's a navigator.
// If it doesn't have router it's an ordinary React component.
const child = screen.router.getPathAndParamsForState(stateRoute);
path = subPath ? `${subPath}/${child.path}` : child.path;
params = child.params ? { ...params, ...child.params } : params;
}
return {
path,
params,
};
},
/**
* Gets an optional action, based on a relative path and query params.
*
* This will return null if there is no action matched
*/
getActionForPathAndParams(path, params) {
if (!path) {
return NavigationActions.navigate({
routeName: initialRouteName,
params,
});
}
return (
order
.map(childId => {
const parts = path.split('/');
const pathToTest = paths[childId];
const partsInTestPath = pathToTest.split('/').length;
const pathPartsToTest = parts.slice(0, partsInTestPath).join('/');
if (pathPartsToTest === pathToTest) {
const childRouter = childRouters[childId];
const action = NavigationActions.navigate({
routeName: childId,
});
if (childRouter && childRouter.getActionForPathAndParams) {
action.action = childRouter.getActionForPathAndParams(
parts.slice(partsInTestPath).join('/'),
params
);
}
if (params) {
action.params = params;
}
return action;
}
return null;
})
.find(action => !!action) ||
order
.map(childId => {
const childRouter = childRouters[childId];
return (
childRouter && childRouter.getActionForPathAndParams(path, params)
);
})
.find(action => !!action) ||
null
);
},
getScreenOptions: createConfigGetter(
routeConfigs,
config.navigationOptions
),
};
};

View File

@@ -1,11 +1,326 @@
import SwitchRouter from './SwitchRouter';
import withDefaultValue from '../utils/withDefaultValue';
import invariant from '../utils/invariant';
import getScreenForRouteName from './getScreenForRouteName';
import createConfigGetter from './createConfigGetter';
import NavigationActions from '../NavigationActions';
import validateRouteConfigMap from './validateRouteConfigMap';
function childrenUpdateWithoutSwitchingIndex(actionType) {
return [
NavigationActions.SET_PARAMS,
NavigationActions.COMPLETE_TRANSITION,
].includes(actionType);
}
export default (routeConfigs, config = {}) => {
config = { ...config };
config = withDefaultValue(config, 'resetOnBlur', false);
config = withDefaultValue(config, 'backBehavior', 'initialRoute');
// Fail fast on invalid route definitions
validateRouteConfigMap(routeConfigs);
const switchRouter = SwitchRouter(routeConfigs, config);
return switchRouter;
const order = config.order || Object.keys(routeConfigs);
const paths = config.paths || {};
const initialRouteParams = config.initialRouteParams;
const initialRouteName = config.initialRouteName || order[0];
const initialRouteIndex = order.indexOf(initialRouteName);
const backBehavior = config.backBehavior || 'initialRoute';
const shouldBackNavigateToInitialRoute = backBehavior === 'initialRoute';
const tabRouters = {};
order.forEach(routeName => {
const routeConfig = routeConfigs[routeName];
paths[routeName] =
typeof routeConfig.path === 'string' ? routeConfig.path : routeName;
tabRouters[routeName] = null;
const screen = getScreenForRouteName(routeConfigs, routeName);
if (screen.router) {
tabRouters[routeName] = screen.router;
}
});
if (initialRouteIndex === -1) {
throw new Error(
`Invalid initialRouteName '${initialRouteName}' for TabRouter. ` +
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
);
}
return {
getStateForAction(action, inputState) {
// Establish a default state
let state = inputState;
if (!state) {
const routes = order.map(routeName => {
const params =
routeName === initialRouteName ? initialRouteParams : undefined;
const tabRouter = tabRouters[routeName];
if (tabRouter) {
const childAction = NavigationActions.init();
return {
...tabRouter.getStateForAction(childAction),
key: routeName,
routeName,
params,
};
}
return {
key: routeName,
routeName,
params,
};
});
state = {
routes,
index: initialRouteIndex,
transitioningFromKey: null,
};
// console.log(`${order.join('-')}: Initial state`, {state});
}
if (action.type === NavigationActions.INIT) {
// Merge any params from the action into all the child routes
const { params } = action;
if (params) {
state.routes = state.routes.map(route => ({
...route,
params: {
...route.params,
...params,
...(route.routeName === initialRouteName
? initialRouteParams
: null),
},
}));
}
}
// Let the current tab handle it
const activeTabLastState = state.routes[state.index];
const activeTabRouter = tabRouters[order[state.index]];
if (activeTabRouter) {
const activeTabState = activeTabRouter.getStateForAction(
action,
activeTabLastState
);
if (!activeTabState && inputState) {
return null;
}
if (activeTabState && activeTabState !== activeTabLastState) {
const routes = [...state.routes];
routes[state.index] = activeTabState;
return {
...state,
routes,
};
}
}
// Handle tab changing. Do this after letting the current tab try to
// handle the action, to allow inner tabs to change first
let activeTabIndex = state.index;
const isBackEligible =
action.key == null || action.key === activeTabLastState.key;
if (action.type === NavigationActions.BACK) {
if (isBackEligible && shouldBackNavigateToInitialRoute) {
activeTabIndex = initialRouteIndex;
} else {
return state;
}
}
let didNavigate = false;
if (action.type === NavigationActions.NAVIGATE) {
const navigateAction = action;
didNavigate = !!order.find((tabId, i) => {
if (tabId === navigateAction.routeName) {
activeTabIndex = i;
return true;
}
return false;
});
if (didNavigate) {
const childState = state.routes[activeTabIndex];
let newChildState;
const tabRouter = tabRouters[action.routeName];
if (action.action) {
newChildState = tabRouter
? tabRouter.getStateForAction(action.action, childState)
: null;
} else if (!tabRouter && action.params) {
newChildState = {
...childState,
params: {
...(childState.params || {}),
...action.params,
},
};
}
if (newChildState && newChildState !== childState) {
const routes = [...state.routes];
routes[activeTabIndex] = newChildState;
return {
...state,
routes,
index: activeTabIndex,
};
}
}
}
if (action.type === NavigationActions.SET_PARAMS) {
const key = action.key;
const lastRoute = state.routes.find(route => route.key === key);
if (lastRoute) {
const params = {
...lastRoute.params,
...action.params,
};
const routes = [...state.routes];
routes[state.routes.indexOf(lastRoute)] = {
...lastRoute,
params,
};
return {
...state,
routes,
};
}
}
if (activeTabIndex !== state.index) {
return {
...state,
index: activeTabIndex,
};
} else if (didNavigate && !inputState) {
return state;
} else if (didNavigate) {
return null;
}
// Let other tabs handle it and switch to the first tab that returns a new state
let index = state.index;
let routes = state.routes;
order.find((tabId, i) => {
const tabRouter = tabRouters[tabId];
if (i === index) {
return false;
}
let tabState = routes[i];
if (tabRouter) {
// console.log(`${order.join('-')}: Processing child router:`, {action, tabState});
tabState = tabRouter.getStateForAction(action, tabState);
}
if (!tabState) {
index = i;
return true;
}
if (tabState !== routes[i]) {
routes = [...routes];
routes[i] = tabState;
index = i;
return true;
}
return false;
});
// console.log(`${order.join('-')}: Processed other tabs:`, {lastIndex: state.index, index});
// Nested routers can be updated after switching tabs with actions such as SET_PARAMS
// and COMPLETE_TRANSITION.
// NOTE: This may be problematic with custom routers because we whitelist the actions
// that can be handled by child routers without automatically changing index.
if (childrenUpdateWithoutSwitchingIndex(action.type)) {
index = state.index;
}
if (index !== state.index || routes !== state.routes) {
return {
...state,
index,
routes,
};
}
return state;
},
getComponentForState(state) {
const routeName = state.routes[state.index].routeName;
invariant(
routeName,
`There is no route defined for index ${state.index}. Check that
that you passed in a navigation state with a valid tab/screen index.`
);
const childRouter = tabRouters[routeName];
if (childRouter) {
return childRouter.getComponentForState(state.routes[state.index]);
}
return getScreenForRouteName(routeConfigs, routeName);
},
getComponentForRouteName(routeName) {
return getScreenForRouteName(routeConfigs, routeName);
},
getPathAndParamsForState(state) {
const route = state.routes[state.index];
const routeName = order[state.index];
const subPath = paths[routeName];
const screen = getScreenForRouteName(routeConfigs, routeName);
let path = subPath;
let params = route.params;
if (screen && screen.router) {
const stateRoute = route;
// If it has a router it's a navigator.
// If it doesn't have router it's an ordinary React component.
const child = screen.router.getPathAndParamsForState(stateRoute);
path = subPath ? `${subPath}/${child.path}` : child.path;
params = child.params ? { ...params, ...child.params } : params;
}
return {
path,
params,
};
},
/**
* Gets an optional action, based on a relative path and query params.
*
* This will return null if there is no action matched
*/
getActionForPathAndParams(path, params) {
return (
order
.map(tabId => {
const parts = path.split('/');
const pathToTest = paths[tabId];
if (parts[0] === pathToTest) {
const tabRouter = tabRouters[tabId];
const action = NavigationActions.navigate({
routeName: tabId,
});
if (tabRouter && tabRouter.getActionForPathAndParams) {
action.action = tabRouter.getActionForPathAndParams(
parts.slice(1).join('/'),
params
);
} else if (params) {
action.params = params;
}
return action;
}
return null;
})
.find(action => !!action) ||
order
.map(tabId => {
const tabRouter = tabRouters[tabId];
return (
tabRouter && tabRouter.getActionForPathAndParams(path, params)
);
})
.find(action => !!action) ||
null
);
},
getScreenOptions: createConfigGetter(
routeConfigs,
config.navigationOptions
),
};
};

View File

@@ -0,0 +1,72 @@
/* eslint react/display-name:0 */
import React from 'react';
import DrawerRouter from '../DrawerRouter';
import NavigationActions from '../../NavigationActions';
const INIT_ACTION = { type: NavigationActions.INIT };
describe('DrawerRouter', () => {
test('Handles basic tab logic', () => {
const ScreenA = () => <div />;
const ScreenB = () => <div />;
const router = DrawerRouter({
Foo: { screen: ScreenA },
Bar: { screen: ScreenB },
});
const state = router.getStateForAction(INIT_ACTION);
const expectedState = {
index: 0,
transitioningFromKey: null,
routes: [
{ key: 'Foo', routeName: 'Foo', params: undefined },
{ key: 'Bar', routeName: 'Bar', params: undefined },
],
isDrawerOpen: false,
};
expect(state).toEqual(expectedState);
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
state
);
const expectedState2 = {
index: 1,
transitioningFromKey: null,
routes: [
{ key: 'Foo', routeName: 'Foo', params: undefined },
{ key: 'Bar', routeName: 'Bar', params: undefined },
],
isDrawerOpen: false,
};
expect(state2).toEqual(expectedState2);
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
});
test('Drawer opens closes and toggles', () => {
const ScreenA = () => <div />;
const ScreenB = () => <div />;
const router = DrawerRouter({
Foo: { screen: ScreenA },
Bar: { screen: ScreenB },
});
const state = router.getStateForAction(INIT_ACTION);
expect(state.isDrawerOpen).toEqual(false);
const state2 = router.getStateForAction(
{ type: NavigationActions.OPEN_DRAWER },
state
);
expect(state2.isDrawerOpen).toEqual(true);
const state3 = router.getStateForAction(
{ type: NavigationActions.CLOSE_DRAWER },
state2
);
expect(state3.isDrawerOpen).toEqual(false);
const state4 = router.getStateForAction(
{ type: NavigationActions.TOGGLE_DRAWER },
state3
);
expect(state4.isDrawerOpen).toEqual(true);
});
});

View File

@@ -1,12 +1,12 @@
/* eslint react/no-multi-comp:0, react/display-name:0 */
/* eslint react/no-multi-comp:0 */
import React from 'react';
import StackRouter from '../StackRouter';
import TabRouter from '../TabRouter';
import SwitchRouter from '../SwitchRouter';
import NavigationActions from '../../NavigationActions';
import addNavigationHelpers from '../../addNavigationHelpers';
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
beforeEach(() => {
@@ -16,7 +16,6 @@ beforeEach(() => {
const ROUTERS = {
TabRouter,
StackRouter,
SwitchRouter,
};
const dummyEventSubscriber = (name, handler) => ({
@@ -27,7 +26,7 @@ Object.keys(ROUTERS).forEach(routerName => {
const Router = ROUTERS[routerName];
describe(`General router features - ${routerName}`, () => {
test(`title is configurable using navigationOptions and getScreenOptions - ${routerName}`, () => {
test('title is configurable using navigationOptions and getScreenOptions', () => {
class FooView extends React.Component {
render() {
return <div />;
@@ -59,132 +58,38 @@ Object.keys(ROUTERS).forEach(routerName => {
];
expect(
router.getScreenOptions(
{
addNavigationHelpers({
state: routes[0],
dispatch: () => false,
addListener: dummyEventSubscriber,
},
}),
{}
).title
).toEqual(undefined);
expect(
router.getScreenOptions(
{
addNavigationHelpers({
state: routes[1],
dispatch: () => false,
addListener: dummyEventSubscriber,
},
}),
{}
).title
).toEqual('BarTitle');
expect(
router.getScreenOptions(
{
addNavigationHelpers({
state: routes[2],
dispatch: () => false,
addListener: dummyEventSubscriber,
},
}),
{}
).title
).toEqual('Baz-123');
});
test(`set params works in ${routerName}`, () => {
class FooView extends React.Component {
render() {
return <div />;
}
}
const router = Router({
Foo: { screen: FooView },
Bar: { screen: FooView },
});
const initState = router.getStateForAction(NavigationActions.init());
const initRoute = initState.routes[initState.index];
expect(initRoute.params).toEqual(undefined);
const state0 = router.getStateForAction(
NavigationActions.setParams({
params: { foo: 42 },
key: initRoute.key,
}),
initState
);
expect(state0.routes[state0.index].params.foo).toEqual(42);
});
});
});
test('Nested navigate behavior test', () => {
const Leaf = () => <div />;
const First = () => <div />;
First.router = StackRouter({
First1: Leaf,
First2: Leaf,
});
const Second = () => <div />;
Second.router = StackRouter({
Second1: Leaf,
Second2: Leaf,
});
const Main = () => <div />;
Main.router = StackRouter({
First,
Second,
});
const TestRouter = SwitchRouter({
Login: Leaf,
Main,
});
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
const state2 = TestRouter.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'First' },
state1
);
expect(state2.index).toEqual(1);
expect(state2.routes[1].index).toEqual(0);
expect(state2.routes[1].routes[0].index).toEqual(0);
const state3 = TestRouter.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'Second2' },
state2
);
expect(state3.index).toEqual(1);
expect(state3.routes[1].index).toEqual(1); // second
expect(state3.routes[1].routes[1].index).toEqual(1); //second.second2
const state4 = TestRouter.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'First',
action: { type: NavigationActions.NAVIGATE, routeName: 'First2' },
},
state3,
true
);
expect(state4.index).toEqual(1); // main
expect(state4.routes[1].index).toEqual(0); // first
expect(state4.routes[1].routes[0].index).toEqual(1); // first2
const state5 = TestRouter.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'First',
action: { type: NavigationActions.NAVIGATE, routeName: 'First1' },
},
state3 // second.second2 is active on state3
);
expect(state5.index).toEqual(1); // main
expect(state5.routes[1].index).toEqual(0); // first
expect(state5.routes[1].routes[0].index).toEqual(0); // first.first1
});
test('Handles no-op actions with tabs within stack router', () => {
const BarView = () => <div />;
const FooTabNavigator = () => <div />;
@@ -230,7 +135,7 @@ test('Handles deep action', () => {
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
const expectedState = {
index: 0,
isTransitioning: false,
transitioningFromKey: false,
key: 'StackRouterRoot',
routes: [
{
@@ -253,70 +158,6 @@ test('Handles deep action', () => {
expect(state2 && state2.routes[1].index).toEqual(1);
});
test('Handles the navigate action with params', () => {
const FooTabNavigator = () => <div />;
FooTabNavigator.router = TabRouter({
Baz: { screen: () => <div /> },
Boo: { screen: () => <div /> },
});
const TestRouter = StackRouter({
Foo: { screen: () => <div /> },
Bar: { screen: FooTabNavigator },
});
const state = TestRouter.getStateForAction({ type: NavigationActions.INIT });
const state2 = TestRouter.getStateForAction(
{
type: NavigationActions.NAVIGATE,
immediate: true,
routeName: 'Bar',
params: { foo: '42' },
},
state
);
expect(state2 && state2.routes[1].params).toEqual({ foo: '42' });
expect(state2 && state2.routes[1].routes).toEqual([
{
key: 'Baz',
routeName: 'Baz',
params: { foo: '42' },
},
{
key: 'Boo',
routeName: 'Boo',
params: { foo: '42' },
},
]);
});
test('Handles the setParams action', () => {
const FooTabNavigator = () => <div />;
FooTabNavigator.router = TabRouter({
Baz: { screen: () => <div /> },
});
const TestRouter = StackRouter({
Foo: { screen: FooTabNavigator },
Bar: { screen: () => <div /> },
});
const state = TestRouter.getStateForAction({ type: NavigationActions.INIT });
const state2 = TestRouter.getStateForAction(
{
type: NavigationActions.SET_PARAMS,
params: { name: 'foobar' },
key: 'Baz',
},
state
);
expect(state2 && state2.index).toEqual(0);
expect(state2 && state2.routes[0].routes).toEqual([
{
key: 'Baz',
routeName: 'Baz',
params: { name: 'foobar' },
},
]);
});
test('Supports lazily-evaluated getScreen', () => {
const BarView = () => <div />;
const FooTabNavigator = () => <div />;
@@ -409,62 +250,3 @@ test('Does not switch tab index when TabRouter child handles COMPLETE_NAVIGATION
expect(stateAfterCompleteTransition.index).toEqual(1);
expect(stateAfterSetParams.index).toEqual(1);
});
test('Inner actions are only unpacked if the current tab matches', () => {
const PlainScreen = () => <div />;
const ScreenA = () => <div />;
const ScreenB = () => <div />;
ScreenB.router = StackRouter({
Baz: { screen: PlainScreen },
Zoo: { screen: PlainScreen },
});
ScreenA.router = StackRouter({
Bar: { screen: PlainScreen },
Boo: { screen: ScreenB },
});
const TestRouter = TabRouter({
Foo: { screen: ScreenA },
});
const screenApreState = {
index: 0,
key: 'Init',
isTransitioning: false,
routeName: 'Foo',
routes: [{ key: 'Init', routeName: 'Bar' }],
};
const preState = {
index: 0,
isTransitioning: false,
routes: [screenApreState],
};
const comparable = state => {
let result = {};
if (typeof state.routeName === 'string') {
result = { ...result, routeName: state.routeName };
}
if (state.routes instanceof Array) {
result = {
...result,
routes: state.routes.map(comparable),
};
}
return result;
};
const action = NavigationActions.navigate({
routeName: 'Boo',
action: NavigationActions.navigate({ routeName: 'Zoo' }),
});
const expectedState = ScreenA.router.getStateForAction(
action,
screenApreState
);
const state = TestRouter.getStateForAction(action, preState);
const innerState = state ? state.routes[0] : state;
expect(expectedState && comparable(expectedState)).toEqual(
innerState && comparable(innerState)
);
});

View File

@@ -3,10 +3,11 @@
import React from 'react';
import StackRouter from '../StackRouter';
import StackActions from '../StackActions';
import NavigationActions from '../../NavigationActions';
import TabRouter from '../TabRouter';
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
import NavigationActions from '../../NavigationActions';
beforeEach(() => {
_TESTING_ONLY_normalize_keys();
});
@@ -91,7 +92,7 @@ describe('StackRouter', () => {
expect(
router.getComponentForState({
index: 0,
isTransitioning: false,
transitioningFromKey: null,
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
@@ -102,7 +103,7 @@ describe('StackRouter', () => {
expect(
router.getComponentForState({
index: 1,
isTransitioning: false,
transitioningFromKey: null,
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
@@ -126,7 +127,7 @@ describe('StackRouter', () => {
expect(
router.getComponentForState({
index: 0,
isTransitioning: false,
transitioningFromKey: null,
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
@@ -137,7 +138,7 @@ describe('StackRouter', () => {
expect(
router.getComponentForState({
index: 1,
isTransitioning: false,
transitioningFromKey: null,
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
@@ -352,7 +353,7 @@ describe('StackRouter', () => {
const initState = TestRouter.getStateForAction(NavigationActions.init());
expect(initState).toEqual({
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'StackRouterRoot',
routes: [{ key: 'id-0', routeName: 'foo' }],
});
@@ -365,38 +366,7 @@ describe('StackRouter', () => {
expect(pushedState.routes[1].routes[1].routeName).toEqual('qux');
});
test('push bubbles up', () => {
const ChildNavigator = () => <div />;
ChildNavigator.router = StackRouter({
Baz: { screen: () => <div /> },
Qux: { screen: () => <div /> },
});
const router = StackRouter({
Foo: { screen: () => <div /> },
Bar: { screen: ChildNavigator },
Bad: { screen: () => <div /> },
});
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
},
state
);
const barKey = state2.routes[1].routes[0].key;
const state3 = router.getStateForAction(
{
type: StackActions.PUSH,
routeName: 'Bad',
},
state2
);
expect(state3 && state3.index).toEqual(2);
expect(state3 && state3.routes.length).toEqual(3);
});
test('pop bubbles up', () => {
test('pop does not bubble up', () => {
const ChildNavigator = () => <div />;
ChildNavigator.router = StackRouter({
Baz: { screen: () => <div /> },
@@ -419,54 +389,51 @@ describe('StackRouter', () => {
const barKey = state2.routes[1].routes[0].key;
const state3 = router.getStateForAction(
{
type: StackActions.POP,
type: NavigationActions.POP,
},
state2
);
expect(state3 && state3.index).toEqual(0);
expect(state3 && state3.index).toEqual(1);
expect(state3 && state3.routes[1].index).toEqual(0);
});
test('Handle navigation to nested navigator', () => {
const state = TestStackRouter.getStateForAction({
type: NavigationActions.INIT,
});
const action = TestStackRouter.getActionForPathAndParams('fo/22/b/hello');
/* $FlowFixMe */
const state2 = TestStackRouter.getStateForAction(action);
expect(state2).toEqual({
index: 0,
isTransitioning: false,
key: 'StackRouterRoot',
routes: [
{
index: 0,
key: 'id-4',
isTransitioning: false,
routeName: 'foo',
params: {
fooThing: '22',
},
routes: [
{
routeName: 'bar',
key: 'id-3',
params: {
barThing: 'hello',
},
},
],
},
],
});
});
test('popToTop bubbles up', () => {
test('push does not bubble up', () => {
const ChildNavigator = () => <div />;
ChildNavigator.router = StackRouter({
Baz: { screen: () => <div /> },
Qux: { screen: () => <div /> },
});
const router = StackRouter({
Foo: { screen: () => <div /> },
Bar: { screen: ChildNavigator },
Bad: { screen: () => <div /> },
});
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
},
state
);
const barKey = state2.routes[1].routes[0].key;
const state3 = router.getStateForAction(
{
type: NavigationActions.PUSH,
routeName: 'Bad',
},
state2
);
expect(state3 && state3.index).toEqual(1);
expect(state3 && state3.routes.length).toEqual(2);
});
test('popToTop does not bubble up', () => {
const ChildNavigator = () => <div />;
ChildNavigator.router = StackRouter({
Baz: { screen: () => <div /> },
Qux: { screen: () => <div /> },
});
const router = StackRouter({
Foo: { screen: () => <div /> },
Bar: { screen: ChildNavigator },
@@ -482,11 +449,12 @@ describe('StackRouter', () => {
const barKey = state2.routes[1].routes[0].key;
const state3 = router.getStateForAction(
{
type: StackActions.POP_TO_TOP,
type: NavigationActions.POP_TO_TOP,
},
state2
);
expect(state3 && state3.index).toEqual(0);
expect(state3 && state3.index).toEqual(1);
expect(state3 && state3.routes[1].index).toEqual(0);
});
test('popToTop targets StackRouter by key if specified', () => {
@@ -510,7 +478,7 @@ describe('StackRouter', () => {
const barKey = state2.routes[1].routes[0].key;
const state3 = router.getStateForAction(
{
type: StackActions.POP_TO_TOP,
type: NavigationActions.POP_TO_TOP,
key: state2.key,
},
state2
@@ -518,44 +486,6 @@ describe('StackRouter', () => {
expect(state3 && state3.index).toEqual(0);
});
test('pop action works as expected', () => {
const TestRouter = StackRouter({
foo: { screen: () => <div /> },
bar: { screen: () => <div /> },
});
const state = {
index: 3,
isTransitioning: false,
routes: [
{ key: 'A', routeName: 'foo' },
{ key: 'B', routeName: 'bar', params: { bazId: '321' } },
{ key: 'C', routeName: 'foo' },
{ key: 'D', routeName: 'bar' },
],
};
const poppedState = TestRouter.getStateForAction(StackActions.pop(), state);
expect(poppedState.routes.length).toBe(3);
expect(poppedState.index).toBe(2);
expect(poppedState.isTransitioning).toBe(true);
const poppedState2 = TestRouter.getStateForAction(
StackActions.pop({ n: 2, immediate: true }),
state
);
expect(poppedState2.routes.length).toBe(2);
expect(poppedState2.index).toBe(1);
expect(poppedState2.isTransitioning).toBe(false);
const poppedState3 = TestRouter.getStateForAction(
StackActions.pop({ n: 5 }),
state
);
expect(poppedState3.routes.length).toBe(1);
expect(poppedState3.index).toBe(0);
expect(poppedState3.isTransitioning).toBe(true);
});
test('popToTop works as expected', () => {
const TestRouter = StackRouter({
foo: { screen: () => <div /> },
@@ -564,7 +494,7 @@ describe('StackRouter', () => {
const state = {
index: 2,
isTransitioning: false,
transitioningFromKey: null,
routes: [
{ key: 'A', routeName: 'foo' },
{ key: 'B', routeName: 'bar', params: { bazId: '321' } },
@@ -572,79 +502,27 @@ describe('StackRouter', () => {
],
};
const poppedState = TestRouter.getStateForAction(
StackActions.popToTop(),
NavigationActions.popToTop(),
state
);
expect(poppedState.routes.length).toBe(1);
expect(poppedState.index).toBe(0);
expect(poppedState.isTransitioning).toBe(true);
expect(poppedState.transitioningFromKey).toBe('C');
const poppedState2 = TestRouter.getStateForAction(
StackActions.popToTop(),
NavigationActions.popToTop(),
poppedState
);
expect(poppedState).toEqual(poppedState2);
const poppedImmediatelyState = TestRouter.getStateForAction(
StackActions.popToTop({ immediate: true }),
NavigationActions.popToTop({ immediate: true }),
state
);
expect(poppedImmediatelyState.routes.length).toBe(1);
expect(poppedImmediatelyState.index).toBe(0);
expect(poppedImmediatelyState.isTransitioning).toBe(false);
expect(poppedImmediatelyState.transitioningFromKey).toBe(null);
});
test('Navigate does not push duplicate routeName', () => {
const TestRouter = StackRouter(
{
foo: { screen: () => <div /> },
bar: { screen: () => <div /> },
},
{ initialRouteName: 'foo' }
);
const initState = TestRouter.getStateForAction(NavigationActions.init());
const barState = TestRouter.getStateForAction(
NavigationActions.navigate({ routeName: 'bar' }),
initState
);
expect(barState.index).toEqual(1);
expect(barState.routes[1].routeName).toEqual('bar');
const navigateOnBarState = TestRouter.getStateForAction(
NavigationActions.navigate({ routeName: 'bar' }),
barState
);
expect(navigateOnBarState).toEqual(null);
});
test('Navigate focuses given routeName if already active in stack', () => {
const TestRouter = StackRouter(
{
foo: { screen: () => <div /> },
bar: { screen: () => <div /> },
baz: { screen: () => <div /> },
},
{ initialRouteName: 'foo' }
);
const initialState = TestRouter.getStateForAction(NavigationActions.init());
const fooBarState = TestRouter.getStateForAction(
NavigationActions.navigate({ routeName: 'bar' }),
initialState
);
const fooBarBazState = TestRouter.getStateForAction(
NavigationActions.navigate({ routeName: 'baz' }),
fooBarState
);
expect(fooBarBazState.index).toEqual(2);
expect(fooBarBazState.routes[2].routeName).toEqual('baz');
const fooState = TestRouter.getStateForAction(
NavigationActions.navigate({ routeName: 'foo' }),
fooBarBazState
);
expect(fooState.index).toEqual(0);
expect(fooState.routes.length).toEqual(1);
expect(fooState.routes[0].routeName).toEqual('foo');
});
test('Navigate pushes duplicate routeName if unique key is provided', () => {
test('Navigate Pushes duplicate routeName', () => {
const TestRouter = StackRouter({
foo: { screen: () => <div /> },
bar: { screen: () => <div /> },
@@ -657,7 +535,7 @@ describe('StackRouter', () => {
expect(pushedState.index).toEqual(1);
expect(pushedState.routes[1].routeName).toEqual('bar');
const pushedTwiceState = TestRouter.getStateForAction(
NavigationActions.navigate({ routeName: 'bar', key: 'new-unique-key!' }),
NavigationActions.navigate({ routeName: 'bar' }),
pushedState
);
expect(pushedTwiceState.index).toEqual(2);
@@ -692,7 +570,6 @@ describe('StackRouter', () => {
state
);
expect(state2.isTransitioning).toEqual(true);
expect(state2.index).toEqual(1);
expect(state2.routes[1].index).toEqual(1);
expect(state2.routes[1].routes[1].index).toEqual(1);
@@ -712,90 +589,28 @@ describe('StackRouter', () => {
NavigationActions.navigate({ routeName: 'foo', key: 'foo' }),
initState
);
expect(pushedState).toEqual(null);
expect(pushedState.index).toEqual(0);
expect(pushedState.routes[0].routeName).toEqual('foo');
});
test('Navigate with key and without it is idempotent', () => {
test('Navigate with key is idempotent', () => {
const TestRouter = StackRouter({
foo: { screen: () => <div /> },
bar: { screen: () => <div /> },
});
const initState = TestRouter.getStateForAction(NavigationActions.init());
for (key of ['a', null]) {
const pushedState = TestRouter.getStateForAction(
NavigationActions.navigate({ routeName: 'bar', key: 'a' }),
initState
);
expect(pushedState.index).toEqual(1);
expect(pushedState.routes[1].routeName).toEqual('bar');
const pushedTwiceState = TestRouter.getStateForAction(
NavigationActions.navigate({ routeName: 'bar', key: 'a' }),
pushedState
);
expect(pushedTwiceState).toEqual(null);
}
});
// https://github.com/react-navigation/react-navigation/issues/4063
test('Navigate on inactive stackrouter is idempotent', () => {
const FirstChildNavigator = () => <div />;
FirstChildNavigator.router = StackRouter({
First1: () => <div />,
First2: () => <div />,
});
const SecondChildNavigator = () => <div />;
SecondChildNavigator.router = StackRouter({
Second1: () => <div />,
Second2: () => <div />,
});
const router = StackRouter({
Leaf: () => <div />,
First: FirstChildNavigator,
Second: SecondChildNavigator,
});
const state = router.getStateForAction({ type: NavigationActions.INIT });
const first = router.getStateForAction(
NavigationActions.navigate({ routeName: 'First2' }),
state
);
const second = router.getStateForAction(
NavigationActions.navigate({ routeName: 'Second2' }),
first
);
const firstAgain = router.getStateForAction(
NavigationActions.navigate({
routeName: 'First2',
params: { debug: true },
}),
second
);
expect(first.routes.length).toEqual(2);
expect(first.index).toEqual(1);
expect(second.routes.length).toEqual(3);
expect(second.index).toEqual(2);
expect(firstAgain.index).toEqual(1);
expect(firstAgain.routes.length).toEqual(2);
});
test('Navigate to current routeName returns null to indicate handled action', () => {
const TestRouter = StackRouter({
foo: { screen: () => <div /> },
bar: { screen: () => <div /> },
});
const initState = TestRouter.getStateForAction(NavigationActions.init());
const navigatedState = TestRouter.getStateForAction(
NavigationActions.navigate({ routeName: 'foo' }),
const pushedState = TestRouter.getStateForAction(
NavigationActions.navigate({ routeName: 'bar', key: 'a' }),
initState
);
expect(navigatedState).toBe(null);
expect(pushedState.index).toEqual(1);
expect(pushedState.routes[1].routeName).toEqual('bar');
const pushedTwiceState = TestRouter.getStateForAction(
NavigationActions.navigate({ routeName: 'bar', key: 'a' }),
pushedState
);
expect(pushedTwiceState.index).toEqual(1);
expect(pushedTwiceState.routes[1].routeName).toEqual('bar');
});
test('Push behaves like navigate, except for key', () => {
@@ -805,39 +620,19 @@ describe('StackRouter', () => {
});
const initState = TestRouter.getStateForAction(NavigationActions.init());
const pushedState = TestRouter.getStateForAction(
StackActions.push({ routeName: 'bar' }),
NavigationActions.push({ routeName: 'bar' }),
initState
);
expect(pushedState.index).toEqual(1);
expect(pushedState.routes[1].routeName).toEqual('bar');
expect(() => {
TestRouter.getStateForAction(
{ type: StackActions.PUSH, routeName: 'bar', key: 'a' },
{ type: NavigationActions.PUSH, routeName: 'bar', key: 'a' },
pushedState
);
}).toThrow();
});
test('Push adds new routes every time', () => {
const TestRouter = StackRouter({
foo: { screen: () => <div /> },
bar: { screen: () => <div /> },
});
const initState = TestRouter.getStateForAction(NavigationActions.init());
const pushedState = TestRouter.getStateForAction(
StackActions.push({ routeName: 'bar' }),
initState
);
expect(pushedState.index).toEqual(1);
expect(pushedState.routes[1].routeName).toEqual('bar');
const secondPushedState = TestRouter.getStateForAction(
StackActions.push({ routeName: 'bar' }),
pushedState
);
expect(secondPushedState.index).toEqual(2);
expect(secondPushedState.routes[2].routeName).toEqual('bar');
});
test('Navigate backwards with key removes leading routes', () => {
const TestRouter = StackRouter({
foo: { screen: () => <div /> },
@@ -883,7 +678,7 @@ describe('StackRouter', () => {
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state).toEqual({
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'StackRouterRoot',
routes: [
{
@@ -911,7 +706,7 @@ describe('StackRouter', () => {
);
expect(state3).toEqual({
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'StackRouterRoot',
routes: [
{
@@ -931,7 +726,7 @@ describe('StackRouter', () => {
NavigationActions.navigate({ routeName: 'foo' })
);
const replacedState = TestRouter.getStateForAction(
StackActions.replace({
NavigationActions.replace({
routeName: 'bar',
params: { meaning: 42 },
key: initState.routes[0].key,
@@ -944,7 +739,7 @@ describe('StackRouter', () => {
expect(replacedState.routes[0].routeName).toEqual('bar');
expect(replacedState.routes[0].params.meaning).toEqual(42);
const replacedState2 = TestRouter.getStateForAction(
StackActions.replace({
NavigationActions.replace({
routeName: 'bar',
key: initState.routes[0].key,
newKey: 'wow',
@@ -978,15 +773,15 @@ describe('StackRouter', () => {
state
);
expect(state2 && state2.index).toEqual(1);
expect(state2 && state2.isTransitioning).toEqual(true);
expect(state2 && state2.transitioningFromKey).toEqual(state.routes[0].key);
const state3 = router.getStateForAction(
{
type: StackActions.COMPLETE_TRANSITION,
type: NavigationActions.COMPLETE_TRANSITION,
},
state2
);
expect(state3 && state3.index).toEqual(1);
expect(state3 && state3.isTransitioning).toEqual(false);
expect(state3 && state3.transitioningFromKey).toEqual(null);
});
test('Handle basic stack logic for components with router', () => {
@@ -1008,7 +803,7 @@ describe('StackRouter', () => {
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state).toEqual({
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'StackRouterRoot',
routes: [
{
@@ -1036,7 +831,7 @@ describe('StackRouter', () => {
);
expect(state3).toEqual({
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'StackRouterRoot',
routes: [
{
@@ -1110,7 +905,7 @@ describe('StackRouter', () => {
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state).toEqual({
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'StackRouterRoot',
routes: [
{
@@ -1134,7 +929,7 @@ describe('StackRouter', () => {
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state).toEqual({
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'StackRouterRoot',
routes: [
{
@@ -1203,51 +998,15 @@ describe('StackRouter', () => {
expect(state2 && state2.routes[0].params).toEqual({ name: 'Qux' });
});
test('Handles the SetParams action for inactive routes', () => {
const router = StackRouter(
{
Foo: {
screen: () => <div />,
},
Bar: {
screen: () => <div />,
},
},
{
initialRouteName: 'Bar',
initialRouteParams: { name: 'Zoo' },
}
);
const initialState = {
index: 1,
routes: [
{
key: 'RouteA',
routeName: 'Foo',
params: { name: 'InitialParam', other: 'Unchanged' },
},
{ key: 'RouteB', routeName: 'Bar', params: {} },
],
};
const state = router.getStateForAction(
{
type: NavigationActions.SET_PARAMS,
params: { name: 'NewParam' },
key: 'RouteA',
},
initialState
);
expect(state.index).toEqual(1);
expect(state.routes[0].params).toEqual({
name: 'NewParam',
other: 'Unchanged',
});
});
test('Handles the setParams action with nested routers', () => {
const ChildNavigator = () => <div />;
ChildNavigator.router = StackRouter({
Baz: { screen: () => <div /> },
const GrandChildNavigator = () => <div />;
GrandChildNavigator.router = StackRouter({
Quux: { screen: () => <div /> },
Corge: { screen: () => <div /> },
});
ChildNavigator.router = TabRouter({
Baz: { screen: GrandChildNavigator },
Qux: { screen: () => <div /> },
});
const router = StackRouter({
@@ -1264,10 +1023,10 @@ describe('StackRouter', () => {
state
);
expect(state2 && state2.index).toEqual(0);
expect(state2 && state2.routes[0].routes).toEqual([
expect(state2 && state2.routes[0].routes[0].routes).toEqual([
{
key: 'id-0',
routeName: 'Baz',
routeName: 'Quux',
params: { name: 'foobar' },
},
]);
@@ -1285,7 +1044,7 @@ describe('StackRouter', () => {
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{
type: StackActions.RESET,
type: NavigationActions.RESET,
actions: [
{
type: NavigationActions.NAVIGATE,
@@ -1320,7 +1079,7 @@ describe('StackRouter', () => {
});
const state1 = router.getStateForAction({ type: NavigationActions.INIT });
const resetAction = {
type: StackActions.RESET,
type: NavigationActions.RESET,
key: 'Bad Key',
actions: [
{
@@ -1353,7 +1112,7 @@ describe('StackRouter', () => {
});
test('Handles the reset action with nested Router', () => {
const ChildRouter = StackRouter({
const ChildRouter = TabRouter({
baz: {
screen: () => <div />,
},
@@ -1373,8 +1132,7 @@ describe('StackRouter', () => {
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{
type: StackActions.RESET,
key: null,
type: NavigationActions.RESET,
actions: [
{
type: NavigationActions.NAVIGATE,
@@ -1426,7 +1184,7 @@ describe('StackRouter', () => {
);
const state3 = router.getStateForAction(
{
type: StackActions.RESET,
type: NavigationActions.RESET,
key: 'Init',
actions: [
{
@@ -1441,7 +1199,7 @@ describe('StackRouter', () => {
);
const state4 = router.getStateForAction(
{
type: StackActions.RESET,
type: NavigationActions.RESET,
key: null,
actions: [
{
@@ -1486,34 +1244,6 @@ describe('StackRouter', () => {
]);
});
test('Navigate action to previous nested StackRouter causes isTransitioning start', () => {
const ChildNavigator = () => <div />;
ChildNavigator.router = StackRouter({
Baz: { screen: () => <div /> },
});
const router = StackRouter({
Bar: { screen: ChildNavigator },
Foo: { screen: () => <div /> },
});
const state = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
immediate: true,
routeName: 'Foo',
},
router.getStateForAction({ type: NavigationActions.INIT })
);
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Baz',
},
state
);
expect(state2.index).toEqual(0);
expect(state2.isTransitioning).toEqual(true);
});
test('Handles the navigate action with params and nested StackRouter as a first action', () => {
const state = TestStackRouter.getStateForAction({
type: NavigationActions.NAVIGATE,
@@ -1544,19 +1274,19 @@ describe('StackRouter', () => {
expect(state).toEqual({
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'StackRouterRoot',
routes: [
{
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'id-2',
params: { code: 'test', foo: 'bar' },
routeName: 'main',
routes: [
{
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'id-1',
params: { code: 'test', foo: 'bar', id: '4' },
routeName: 'profile',
@@ -1603,19 +1333,19 @@ describe('StackRouter', () => {
expect(state2).toEqual({
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'StackRouterRoot',
routes: [
{
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'id-5',
params: { code: '', foo: 'bar' },
routeName: 'main',
routes: [
{
index: 0,
isTransitioning: false,
transitioningFromKey: null,
key: 'id-4',
params: { code: '', foo: 'bar', id: '4' },
routeName: 'profile',
@@ -1634,6 +1364,42 @@ describe('StackRouter', () => {
});
});
test('Handles the navigate action with params and nested TabRouter', () => {
const ChildNavigator = () => <div />;
ChildNavigator.router = TabRouter({
Baz: { screen: () => <div /> },
Boo: { screen: () => <div /> },
});
const router = StackRouter({
Foo: { screen: () => <div /> },
Bar: { screen: ChildNavigator },
});
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
immediate: true,
routeName: 'Bar',
params: { foo: '42' },
},
state
);
expect(state2 && state2.routes[1].params).toEqual({ foo: '42' });
expect(state2 && state2.routes[1].routes).toEqual([
{
key: 'Baz',
routeName: 'Baz',
params: { foo: '42' },
},
{
key: 'Boo',
routeName: 'Boo',
params: { foo: '42' },
},
]);
});
test('Handles empty URIs', () => {
const router = StackRouter(
{
@@ -1682,7 +1448,7 @@ describe('StackRouter', () => {
const state = {
index: 0,
isTransitioning: false,
transitioningFromKey: null,
routes: [
{
index: 1,
@@ -1898,136 +1664,21 @@ test('Handles deep navigate completion action', () => {
},
state
);
expect(state2 && state2.index).toEqual(0);
expect(state2 && state2.isTransitioning).toEqual(false);
expect(state2 && state2.routes[0].index).toEqual(1);
expect(state2 && state2.routes[0].isTransitioning).toEqual(true);
expect(state2.index).toEqual(0);
expect(state2.transitioningFromKey).toEqual(null);
expect(state2.routes[0].index).toEqual(1);
expect(state2.routes[0].transitioningFromKey).toEqual(
state.routes[0].routes[state.routes[0].index].key
);
expect(!!key).toEqual(true);
const state3 = router.getStateForAction(
{
type: StackActions.COMPLETE_TRANSITION,
type: NavigationActions.COMPLETE_TRANSITION,
},
state2
);
expect(state3 && state3.index).toEqual(0);
expect(state3 && state3.isTransitioning).toEqual(false);
expect(state3 && state3.routes[0].index).toEqual(1);
expect(state3 && state3.routes[0].isTransitioning).toEqual(false);
});
test('order of handling navigate action is correct for nested stackrouters', () => {
const Screen = () => <div />;
const NestedStack = () => <div />;
let nestedRouter = StackRouter({
Foo: Screen,
Bar: Screen,
});
NestedStack.router = nestedRouter;
let router = StackRouter(
{
NestedStack,
Bar: Screen,
Baz: Screen,
},
{
initialRouteName: 'Baz',
}
);
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state.routes[state.index].routeName).toEqual('Baz');
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
},
state
);
expect(state2.routes[state2.index].routeName).toEqual('Bar');
const state3 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Baz',
},
state2
);
expect(state3.routes[state3.index].routeName).toEqual('Baz');
const state4 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Foo',
},
state3
);
let activeState4 = state4.routes[state4.index];
expect(activeState4.routeName).toEqual('NestedStack');
expect(activeState4.routes[activeState4.index].routeName).toEqual('Foo');
const state5 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
},
state4
);
let activeState5 = state5.routes[state5.index];
expect(activeState5.routeName).toEqual('NestedStack');
expect(activeState5.routes[activeState5.index].routeName).toEqual('Bar');
});
test('order of handling navigate action is correct for nested stackrouters', () => {
const Screen = () => <div />;
const NestedStack = () => <div />;
const OtherNestedStack = () => <div />;
let nestedRouter = StackRouter({ Foo: Screen, Bar: Screen });
let otherNestedRouter = StackRouter({ Foo: Screen });
NestedStack.router = nestedRouter;
OtherNestedStack.router = otherNestedRouter;
let router = StackRouter(
{
NestedStack,
OtherNestedStack,
Bar: Screen,
},
{
initialRouteName: 'OtherNestedStack',
}
);
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state.routes[state.index].routeName).toEqual('OtherNestedStack');
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
},
state
);
expect(state2.routes[state2.index].routeName).toEqual('Bar');
const state3 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'NestedStack',
},
state2
);
const state4 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
},
state3
);
let activeState4 = state4.routes[state4.index];
expect(activeState4.routeName).toEqual('NestedStack');
expect(activeState4.routes[activeState4.index].routeName).toEqual('Bar');
expect(state3.index).toEqual(0);
expect(state3.transitioningFromKey).toEqual(null);
expect(state3.routes[0].index).toEqual(1);
expect(state3.routes[0].transitioningFromKey).toEqual(null);
});

View File

@@ -1,252 +0,0 @@
/* eslint react/display-name:0 */
import React from 'react';
import SwitchRouter from '../SwitchRouter';
import StackRouter from '../StackRouter';
import NavigationActions from '../../NavigationActions';
describe('SwitchRouter', () => {
test('resets the route when unfocusing a tab by default', () => {
const router = getExampleRouter();
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'A2' },
state
);
expect(state2.routes[0].index).toEqual(1);
expect(state2.routes[0].routes.length).toEqual(2);
const state3 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
state2
);
expect(state3.routes[0].index).toEqual(0);
expect(state3.routes[0].routes.length).toEqual(1);
});
test('does not reset the route on unfocus if resetOnBlur is false', () => {
const router = getExampleRouter({ resetOnBlur: false });
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'A2' },
state
);
expect(state2.routes[0].index).toEqual(1);
expect(state2.routes[0].routes.length).toEqual(2);
const state3 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
state2
);
expect(state3.routes[0].index).toEqual(1);
expect(state3.routes[0].routes.length).toEqual(2);
});
test('ignores back by default', () => {
const router = getExampleRouter();
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
state
);
expect(state2.index).toEqual(1);
const state3 = router.getStateForAction(
{ type: NavigationActions.BACK },
state2
);
expect(state3.index).toEqual(1);
});
test('handles back if given a backBehavior', () => {
const router = getExampleRouter({ backBehavior: 'initialRoute' });
const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction(
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
state
);
expect(state2.index).toEqual(1);
const state3 = router.getStateForAction(
{ type: NavigationActions.BACK },
state2
);
expect(state3.index).toEqual(0);
});
test('paths option on SwitchRouter overrides path from route config', () => {
const router = getExampleRouter({ paths: { A: 'overridden' } });
const action = router.getActionForPathAndParams('overridden', {});
expect(action.type).toEqual(NavigationActions.NAVIGATE);
expect(action.routeName).toEqual('A');
});
test('provides correct action for getActionForPathAndParams', () => {
const router = getExampleRouter({ backBehavior: 'initialRoute' });
const action = router.getActionForPathAndParams('A1', { foo: 'bar' });
expect(action.type).toEqual(NavigationActions.NAVIGATE);
expect(action.routeName).toEqual('A1');
const action1 = router.getActionForPathAndParams('', {});
expect(action1.type).toEqual(NavigationActions.NAVIGATE);
expect(action1.routeName).toEqual('A');
const action2 = router.getActionForPathAndParams(null, {});
expect(action2.type).toEqual(NavigationActions.NAVIGATE);
expect(action2.routeName).toEqual('A');
const action3 = router.getActionForPathAndParams('great/path', {
foo: 'baz',
});
expect(action3).toEqual({
type: NavigationActions.NAVIGATE,
routeName: 'B',
params: { foo: 'baz' },
action: {
type: NavigationActions.NAVIGATE,
routeName: 'B1',
params: { foo: 'baz' },
},
});
const action4 = router.getActionForPathAndParams('great/path/B2', {
foo: 'baz',
});
expect(action4).toEqual({
type: NavigationActions.NAVIGATE,
routeName: 'B',
params: { foo: 'baz' },
action: {
type: NavigationActions.NAVIGATE,
routeName: 'B2',
params: { foo: 'baz' },
},
});
});
test('order of handling navigate action is correct for nested switchrouters', () => {
// router = switch({ Nested: switch({ Foo, Bar }), Other: switch({ Foo }), Bar })
// if we are focused on Other and navigate to Bar, what should happen?
const Screen = () => <div />;
const NestedSwitch = () => <div />;
const OtherNestedSwitch = () => <div />;
let nestedRouter = SwitchRouter({ Foo: Screen, Bar: Screen });
let otherNestedRouter = SwitchRouter({ Foo: Screen });
NestedSwitch.router = nestedRouter;
OtherNestedSwitch.router = otherNestedRouter;
let router = SwitchRouter(
{
NestedSwitch,
OtherNestedSwitch,
Bar: Screen,
},
{
initialRouteName: 'OtherNestedSwitch',
}
);
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state.routes[state.index].routeName).toEqual('OtherNestedSwitch');
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
},
state
);
expect(state2.routes[state2.index].routeName).toEqual('Bar');
const state3 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'NestedSwitch',
},
state2
);
const state4 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
},
state3
);
let activeState4 = state4.routes[state4.index];
expect(activeState4.routeName).toEqual('NestedSwitch');
expect(activeState4.routes[activeState4.index].routeName).toEqual('Bar');
});
// https://github.com/react-navigation/react-navigation.github.io/issues/117#issuecomment-385597628
test('order of handling navigate action is correct for nested stackrouters', () => {
const Screen = () => <div />;
const MainStack = () => <div />;
const LoginStack = () => <div />;
MainStack.router = StackRouter({ Home: Screen, Profile: Screen });
LoginStack.router = StackRouter({ Form: Screen, ForgotPassword: Screen });
let router = SwitchRouter(
{
Home: Screen,
Login: LoginStack,
Main: MainStack,
},
{
initialRouteName: 'Login',
}
);
const state = router.getStateForAction({ type: NavigationActions.INIT });
expect(state.routes[state.index].routeName).toEqual('Login');
const state2 = router.getStateForAction(
{
type: NavigationActions.NAVIGATE,
routeName: 'Home',
},
state
);
expect(state2.routes[state2.index].routeName).toEqual('Home');
});
});
const getExampleRouter = (config = {}) => {
const PlainScreen = () => <div />;
const StackA = () => <div />;
const StackB = () => <div />;
StackA.router = StackRouter({
A1: PlainScreen,
A2: PlainScreen,
});
StackB.router = StackRouter({
B1: PlainScreen,
B2: PlainScreen,
});
const router = SwitchRouter(
{
A: {
screen: StackA,
path: '',
},
B: {
screen: StackB,
path: 'great/path',
},
},
{
initialRouteName: 'A',
...config,
}
);
return router;
};

View File

@@ -2,8 +2,8 @@
import React from 'react';
import TabRouter from '../TabRouter';
import StackRouter from '../StackRouter';
import StackActions from '../../routers/StackActions';
import NavigationActions from '../../NavigationActions';
const INIT_ACTION = { type: NavigationActions.INIT };
@@ -140,46 +140,6 @@ describe('TabRouter', () => {
expect(state2 && state2.routes[0].params).toEqual({ name: 'Qux' });
});
test('Handles the SetParams action for inactive routes', () => {
const router = TabRouter(
{
Foo: {
screen: () => <div />,
},
Bar: {
screen: () => <div />,
},
},
{
initialRouteName: 'Bar',
}
);
const initialState = {
index: 1,
routes: [
{
key: 'RouteA',
routeName: 'Foo',
params: { name: 'InitialParam', other: 'Unchanged' },
},
{ key: 'RouteB', routeName: 'Bar', params: {} },
],
};
const state = router.getStateForAction(
{
type: NavigationActions.SET_PARAMS,
params: { name: 'NewParam' },
key: 'RouteA',
},
initialState
);
expect(state.index).toEqual(1);
expect(state.routes[0].params).toEqual({
name: 'NewParam',
other: 'Unchanged',
});
});
test('getStateForAction returns null when navigating to same tab', () => {
const router = TabRouter(
{ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig },
@@ -221,7 +181,6 @@ describe('TabRouter', () => {
const navAction = {
type: NavigationActions.NAVIGATE,
routeName: 'Baz',
params: { foo: '42' },
action: {
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
@@ -392,7 +351,7 @@ describe('TabRouter', () => {
});
const MidNavigator = () => <div />;
MidNavigator.router = TabRouter({
Fee: { screen: ChildNavigator0 },
Foo: { screen: ChildNavigator0 },
Bar: { screen: ChildNavigator1 },
});
const router = TabRouter({
@@ -412,8 +371,8 @@ describe('TabRouter', () => {
routes: [
{
index: 0,
key: 'Fee',
routeName: 'Fee',
key: 'Foo',
routeName: 'Foo',
isTransitioning: false,
routes: [
{ key: 'Boo', routeName: 'Boo' },
@@ -451,8 +410,8 @@ describe('TabRouter', () => {
routes: [
{
index: 0,
key: 'Fee',
routeName: 'Fee',
key: 'Foo',
routeName: 'Foo',
isTransitioning: false,
routes: [
{ key: 'Boo', routeName: 'Boo' },
@@ -485,10 +444,7 @@ describe('TabRouter', () => {
action: {
type: NavigationActions.NAVIGATE,
routeName: 'Bar',
action: {
type: NavigationActions.NAVIGATE,
routeName: 'Zap',
},
action: { type: NavigationActions.NAVIGATE, routeName: 'Zap' },
},
});
expect(state4).toEqual({
@@ -503,8 +459,8 @@ describe('TabRouter', () => {
routes: [
{
index: 0,
key: 'Fee',
routeName: 'Fee',
key: 'Foo',
routeName: 'Foo',
isTransitioning: false,
routes: [
{ key: 'Boo', routeName: 'Boo' },
@@ -734,15 +690,56 @@ describe('TabRouter', () => {
expect(state2).toEqual(state0);
});
test('pop action works as expected', () => {
const TestRouter = StackRouter({
foo: { screen: () => <div /> },
bar: { screen: () => <div /> },
});
const state = {
index: 3,
isTransitioning: false,
routes: [
{ key: 'A', routeName: 'foo' },
{ key: 'B', routeName: 'bar', params: { bazId: '321' } },
{ key: 'C', routeName: 'foo' },
{ key: 'D', routeName: 'bar' },
],
};
const poppedState = TestRouter.getStateForAction(
NavigationActions.pop(),
state
);
expect(poppedState.routes.length).toBe(3);
expect(poppedState.index).toBe(2);
expect(poppedState.isTransitioning).toBe(true);
const poppedState2 = TestRouter.getStateForAction(
NavigationActions.pop({ n: 2, immediate: true }),
state
);
expect(poppedState2.routes.length).toBe(2);
expect(poppedState2.index).toBe(1);
expect(poppedState2.isTransitioning).toBe(false);
const poppedState3 = TestRouter.getStateForAction(
NavigationActions.pop({ n: 5 }),
state
);
expect(poppedState3.routes.length).toBe(1);
expect(poppedState3.index).toBe(0);
expect(poppedState3.isTransitioning).toBe(true);
});
test('Inner actions are only unpacked if the current tab matches', () => {
const PlainScreen = () => <div />;
const ScreenA = () => <div />;
const ScreenB = () => <div />;
ScreenB.router = TabRouter({
ScreenB.router = StackRouter({
Baz: { screen: PlainScreen },
Zoo: { screen: PlainScreen },
});
ScreenA.router = TabRouter({
ScreenA.router = StackRouter({
Bar: { screen: PlainScreen },
Boo: { screen: ScreenB },
});
@@ -751,10 +748,10 @@ describe('TabRouter', () => {
});
const screenApreState = {
index: 0,
key: 'Foo',
key: 'Init',
isTransitioning: false,
routeName: 'Foo',
routes: [{ key: 'Bar', routeName: 'Bar' }],
routes: [{ key: 'Init', routeName: 'Bar' }],
};
const preState = {
index: 0,
@@ -780,6 +777,7 @@ describe('TabRouter', () => {
routeName: 'Boo',
action: NavigationActions.navigate({ routeName: 'Zoo' }),
});
const expectedState = ScreenA.router.getStateForAction(
action,
screenApreState
@@ -787,25 +785,8 @@ describe('TabRouter', () => {
const state = router.getStateForAction(action, preState);
const innerState = state ? state.routes[0] : state;
expect(innerState.routes[1].index).toEqual(1);
expect(expectedState && comparable(expectedState)).toEqual(
innerState && comparable(innerState)
);
const noMatchAction = NavigationActions.navigate({
routeName: 'Qux',
action: NavigationActions.navigate({ routeName: 'Zoo' }),
});
const expectedState2 = ScreenA.router.getStateForAction(
noMatchAction,
screenApreState
);
const state2 = router.getStateForAction(noMatchAction, preState);
const innerState2 = state2 ? state2.routes[0] : state2;
expect(innerState2.routes[1].index).toEqual(0);
expect(expectedState2 && comparable(expectedState2)).toEqual(
innerState2 && comparable(innerState2)
);
});
});

View File

@@ -1,5 +1,6 @@
import { Component } from 'react';
import createConfigGetter from '../createConfigGetter';
import addNavigationHelpers from '../../addNavigationHelpers';
const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
remove: () => {},
@@ -66,81 +67,81 @@ test('should get config for screen', () => {
expect(
getScreenOptions(
{
addNavigationHelpers({
state: routes[0],
dispatch: () => false,
addListener: dummyEventSubscriber,
},
}),
{}
).title
).toEqual('Welcome anonymous');
expect(
getScreenOptions(
{
addNavigationHelpers({
state: routes[1],
dispatch: () => false,
addListener: dummyEventSubscriber,
},
}),
{}
).title
).toEqual('Welcome jane');
expect(
getScreenOptions(
{
addNavigationHelpers({
state: routes[0],
dispatch: () => false,
addListener: dummyEventSubscriber,
},
}),
{}
).gesturesEnabled
).toEqual(true);
expect(
getScreenOptions(
{
addNavigationHelpers({
state: routes[2],
dispatch: () => false,
addListener: dummyEventSubscriber,
},
}),
{}
).title
).toEqual('Settings!!!');
expect(
getScreenOptions(
{
addNavigationHelpers({
state: routes[2],
dispatch: () => false,
addListener: dummyEventSubscriber,
},
}),
{}
).gesturesEnabled
).toEqual(false);
expect(
getScreenOptions(
{
addNavigationHelpers({
state: routes[3],
dispatch: () => false,
addListener: dummyEventSubscriber,
},
}),
{}
).title
).toEqual('10 new notifications');
expect(
getScreenOptions(
{
addNavigationHelpers({
state: routes[3],
dispatch: () => false,
addListener: dummyEventSubscriber,
},
}),
{}
).gesturesEnabled
).toEqual(true);
expect(
getScreenOptions(
{
addNavigationHelpers({
state: routes[4],
dispatch: () => false,
addListener: dummyEventSubscriber,
},
}),
{}
).gesturesEnabled
).toEqual(false);
@@ -163,11 +164,11 @@ test('should throw if the route does not exist', () => {
expect(() =>
getScreenOptions(
{
addNavigationHelpers({
state: routes[0],
dispatch: () => false,
addListener: dummyEventSubscriber,
},
}),
{}
)
).toThrowError(

View File

@@ -38,8 +38,7 @@ export default (routeConfigs, navigatorScreenConfig) => (
const routeConfig = routeConfigs[route.routeName];
const routeScreenConfig =
routeConfig === Component ? null : routeConfig.navigationOptions;
const routeScreenConfig = routeConfig.navigationOptions;
const componentScreenConfig = Component.navigationOptions;
const configOptions = { navigation, screenProps: screenProps || {} };

View File

@@ -1,46 +0,0 @@
import NavigationActions from '../NavigationActions';
import invariant from '../utils/invariant';
const getNavigationActionCreators = route => {
return {
goBack: key => {
let actualizedKey = key;
if (key === undefined && route.key) {
invariant(typeof route.key === 'string', 'key should be a string');
actualizedKey = route.key;
}
return NavigationActions.back({ key: actualizedKey });
},
navigate: (navigateTo, params, action) => {
if (typeof navigateTo === 'string') {
return NavigationActions.navigate({
routeName: navigateTo,
params,
action,
});
}
invariant(
typeof navigateTo === 'object',
'Must navigateTo an object or a string'
);
invariant(
params == null,
'Params must not be provided to .navigate() when specifying an object'
);
invariant(
action == null,
'Child action must not be provided to .navigate() when specifying an object'
);
return NavigationActions.navigate(navigateTo);
},
setParams: params => {
invariant(
route.key && typeof route.key === 'string',
'setParams cannot be called by root navigator'
);
return NavigationActions.setParams({ params, key: route.key });
},
};
};
export default getNavigationActionCreators;

View File

@@ -21,24 +21,25 @@ function validateRouteConfigMap(routeConfigs) {
typeof screenComponent !== 'string' &&
!routeConfig.getScreen)
) {
throw new Error(`The component for route '${routeName}' must be a React component. For example:
import MyScreen from './MyScreen';
...
${routeName}: MyScreen,
}
You can also use a navigator:
import MyNavigator from './MyNavigator';
...
${routeName}: MyNavigator,
}`);
throw new Error(
`The component for route '${routeName}' must be a ` +
'React component. For example:\n\n' +
"import MyScreen from './MyScreen';\n" +
'...\n' +
`${routeName}: MyScreen,\n` +
'}\n\n' +
'You can also use a navigator:\n\n' +
"import MyNavigator from './MyNavigator';\n" +
'...\n' +
`${routeName}: MyNavigator,\n` +
'}'
);
}
if (routeConfig.screen && routeConfig.getScreen) {
throw new Error(
`Route '${routeName}' should declare a screen or a getScreen, not both.`
`Route '${routeName}' should declare a screen or ` +
'a getScreen, not both.'
);
}
});

View File

@@ -1,3 +0,0 @@
export default function docsUrl(path) {
return `https://reactnavigation.org/docs/${path}`;
}

View File

@@ -1,3 +1,14 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
/**
* Use invariant() to assert state which your program assumes to be true.
*

View File

@@ -1,5 +1,19 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @typechecks
*
*/
/*eslint-disable no-self-compare */
'use strict';
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
@@ -57,4 +71,4 @@ function shallowEqual(objA, objB) {
return true;
}
export default shallowEqual;
module.exports = shallowEqual;

Some files were not shown because too many files have changed in this diff Show More