Removed react-native-router-flux and added custom navigation logic (#102)
* Removed react-native-router-flux and replaced it with custom navigation * Split Logout component to have a container * Replaced RootContainer with RootLayout * Removed lodash from package.json * Removed unnecessary state from Logout component * Added placeholder for RHS search * Renamed routes to scenes and moved some logic to there * Added Root route to handle any initialization on app load * Added unit tests for navigation reducer * Fixed eslint errors * Removed commented out code from Router * Renamed Channel route to ChannelView to avoid confusion * Used a separate index.js for component containers * Simplified mapStateToProps for RootLayoutContainer * Removed file accidentally added in merge conflict
This commit is contained in:
parent
9125d0cac2
commit
8732febd8c
37 changed files with 879 additions and 232 deletions
|
|
@ -7,7 +7,6 @@
|
|||
"intl": "1.2.5",
|
||||
"isomorphic-fetch": "2.2.1",
|
||||
"keymirror": "0.1.1",
|
||||
"lodash": "4.16.4",
|
||||
"react": "15.3.2",
|
||||
"react-addons-pure-render-mixin": "15.3.2",
|
||||
"react-intl": "2.1.5",
|
||||
|
|
@ -15,7 +14,6 @@
|
|||
"react-native-button": "1.7.1",
|
||||
"react-native-drawer": "2.3.0",
|
||||
"react-native-keyboard-spacer": "0.3.0",
|
||||
"react-native-router-flux": "3.36.0",
|
||||
"react-native-vector-icons": "2.1.0",
|
||||
"react-redux": "4.4.5",
|
||||
"redux": "3.6.0",
|
||||
|
|
|
|||
68
src/actions/navigation/index.js
Normal file
68
src/actions/navigation/index.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {NavigationTypes} from 'constants';
|
||||
import Routes from 'navigation/routes';
|
||||
|
||||
export function goBack() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_POP
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToLogin() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.Login
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToSelectTeam() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.SelectTeam
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToChannelView() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.ChannelView
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToRecentMentions() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: {
|
||||
...Routes.Search,
|
||||
props: {
|
||||
searchType: 'recent_mentions'
|
||||
}
|
||||
}
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToFlaggedPosts() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: {
|
||||
...Routes.Search,
|
||||
props: {
|
||||
searchType: 'flagged_posts'
|
||||
}
|
||||
}
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
15
src/actions/views/root.js
Normal file
15
src/actions/views/root.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {NavigationTypes} from 'constants';
|
||||
import Routes from 'navigation/routes';
|
||||
|
||||
export function goToSelectServer() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_RESET,
|
||||
routes: [Routes.SelectServer],
|
||||
index: 0
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import {TouchableHighlight, Text} from 'react-native';
|
||||
import {logout} from 'actions/users';
|
||||
import {RequestStatus} from 'constants';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
import {Actions as Routes} from 'react-native-router-flux';
|
||||
|
||||
const propTypes = {
|
||||
logout: PropTypes.object.isRequired,
|
||||
actions: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
class Logout extends Component {
|
||||
static propTypes = propTypes;
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.logout.status === RequestStatus.STARTED &&
|
||||
nextProps.logout.status === RequestStatus.SUCCESS) {
|
||||
Routes.popTo('goToSelectServer');
|
||||
}
|
||||
}
|
||||
|
||||
logout = () => this.props.actions.logout();
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TouchableHighlight onPress={this.logout}>
|
||||
<Text>{'logout'}</Text>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
logout: state.requests.users.logout
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({logout}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Logout);
|
||||
6
src/components/logout/index.js
Normal file
6
src/components/logout/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import LogoutContainer from './logout_container';
|
||||
|
||||
export default LogoutContainer;
|
||||
24
src/components/logout/logout.js
Normal file
24
src/components/logout/logout.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {TouchableHighlight} from 'react-native';
|
||||
import FormattedText from 'components/formatted_text';
|
||||
|
||||
export default class Logout extends React.Component {
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TouchableHighlight onPress={this.props.actions.logout}>
|
||||
<FormattedText
|
||||
id='sidebar_right_menu.logout'
|
||||
defaultMessage='Logout'
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
}
|
||||
17
src/components/logout/logout_container.js
Normal file
17
src/components/logout/logout_container.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {logout} from 'actions/users';
|
||||
|
||||
import Logout from './logout.js';
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({logout}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(Logout);
|
||||
42
src/components/right_sidebar_menu/components/item.js
Normal file
42
src/components/right_sidebar_menu/components/item.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {StyleSheet, TouchableHighlight, View} from 'react-native';
|
||||
|
||||
const Styles = StyleSheet.create({
|
||||
item: {
|
||||
alignItems: 'center',
|
||||
height: 40,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
flex: 1,
|
||||
flexDirection: 'row'
|
||||
}
|
||||
});
|
||||
|
||||
export default class Item extends React.Component {
|
||||
static propTypes = {
|
||||
children: React.PropTypes.node,
|
||||
onPress: React.PropTypes.func,
|
||||
style: React.PropTypes.oneOfType([
|
||||
React.PropTypes.object,
|
||||
React.PropTypes.array
|
||||
])
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TouchableHighlight
|
||||
underlayColor='rgba(255, 255, 255, 0.3)'
|
||||
onPress={this.props.onPress}
|
||||
style={this.props.style}
|
||||
>
|
||||
<View style={Styles.item}>
|
||||
{this.props.children}
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
}
|
||||
6
src/components/right_sidebar_menu/index.js
Normal file
6
src/components/right_sidebar_menu/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import RightSidebarMenuContainer from './right_sidebar_menu_container';
|
||||
|
||||
export default RightSidebarMenuContainer;
|
||||
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import FormattedText from './formatted_text.js';
|
||||
import FormattedText from 'components/formatted_text';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import Item from './components/item';
|
||||
import {
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
|
|
@ -19,14 +19,6 @@ const Styles = StyleSheet.create({
|
|||
flex: 1,
|
||||
paddingTop: 20
|
||||
},
|
||||
item: {
|
||||
alignItems: 'center',
|
||||
height: 40,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
flex: 1,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
itemText: {
|
||||
color: 'white'
|
||||
},
|
||||
|
|
@ -45,14 +37,29 @@ const Styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
export default class RightSidebarMenu extends React.Component {
|
||||
handlePress() {
|
||||
console.log('press'); // eslint-disable-line no-console
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.shape({
|
||||
goToFlaggedPosts: React.PropTypes.func.isRequired,
|
||||
goToRecentMentions: React.PropTypes.func.isRequired,
|
||||
logout: React.PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
onClose: React.PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
goToRecentMentions = () => {
|
||||
this.props.onClose();
|
||||
this.props.actions.goToRecentMentions();
|
||||
}
|
||||
|
||||
goToFlaggedPosts = () => {
|
||||
this.props.onClose();
|
||||
this.props.actions.goToFlaggedPosts();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ScrollView style={Styles.container}>
|
||||
<Item onPress={this.handlePress}>
|
||||
<Item onPress={this.goToRecentMentions}>
|
||||
<Text style={[Styles.icon, Styles.mentionIcon]}>{'@'}</Text>
|
||||
<FormattedText
|
||||
style={Styles.itemText}
|
||||
|
|
@ -60,7 +67,7 @@ export default class RightSidebarMenu extends React.Component {
|
|||
defaultMessage='Recent Mentions'
|
||||
/>
|
||||
</Item>
|
||||
<Item onPress={this.handlePress}>
|
||||
<Item onPress={this.goToFlaggedPosts}>
|
||||
<Icon
|
||||
style={Styles.icon}
|
||||
name='flag'
|
||||
|
|
@ -72,7 +79,7 @@ export default class RightSidebarMenu extends React.Component {
|
|||
/>
|
||||
</Item>
|
||||
<Divider/>
|
||||
<Item onPress={this.handlePress}>
|
||||
<Item>
|
||||
<Icon
|
||||
style={Styles.icon}
|
||||
name='cog'
|
||||
|
|
@ -83,7 +90,7 @@ export default class RightSidebarMenu extends React.Component {
|
|||
defaultMessage='Account Settings'
|
||||
/>
|
||||
</Item>
|
||||
<Item onPress={this.handlePress}>
|
||||
<Item>
|
||||
<Icon
|
||||
style={Styles.icon}
|
||||
name='user-plus'
|
||||
|
|
@ -94,7 +101,7 @@ export default class RightSidebarMenu extends React.Component {
|
|||
defaultMessage='Invite New Member'
|
||||
/>
|
||||
</Item>
|
||||
<Item onPress={this.handlePress}>
|
||||
<Item>
|
||||
<Icon
|
||||
style={Styles.icon}
|
||||
name='link'
|
||||
|
|
@ -106,7 +113,7 @@ export default class RightSidebarMenu extends React.Component {
|
|||
/>
|
||||
</Item>
|
||||
<Divider/>
|
||||
<Item onPress={this.handlePress}>
|
||||
<Item>
|
||||
<Icon
|
||||
style={Styles.icon}
|
||||
name='globe'
|
||||
|
|
@ -117,7 +124,7 @@ export default class RightSidebarMenu extends React.Component {
|
|||
defaultMessage='Team Settings'
|
||||
/>
|
||||
</Item>
|
||||
<Item onPress={this.handlePress}>
|
||||
<Item>
|
||||
<Icon
|
||||
style={Styles.icon}
|
||||
name='users'
|
||||
|
|
@ -128,7 +135,7 @@ export default class RightSidebarMenu extends React.Component {
|
|||
defaultMessage='Manage Members'
|
||||
/>
|
||||
</Item>
|
||||
<Item onPress={this.handlePress}>
|
||||
<Item>
|
||||
<Icon
|
||||
style={Styles.icon}
|
||||
name='exchange'
|
||||
|
|
@ -140,7 +147,7 @@ export default class RightSidebarMenu extends React.Component {
|
|||
/>
|
||||
</Item>
|
||||
<Divider/>
|
||||
<Item onPress={this.handlePress}>
|
||||
<Item>
|
||||
<Icon
|
||||
style={Styles.icon}
|
||||
name='question'
|
||||
|
|
@ -151,7 +158,7 @@ export default class RightSidebarMenu extends React.Component {
|
|||
defaultMessage='Help'
|
||||
/>
|
||||
</Item>
|
||||
<Item onPress={this.handlePress}>
|
||||
<Item>
|
||||
<Icon
|
||||
style={Styles.icon}
|
||||
name='phone'
|
||||
|
|
@ -162,7 +169,7 @@ export default class RightSidebarMenu extends React.Component {
|
|||
defaultMessage='Report a Problem'
|
||||
/>
|
||||
</Item>
|
||||
<Item onPress={this.handlePress}>
|
||||
<Item>
|
||||
<Icon
|
||||
style={Styles.icon}
|
||||
name='info'
|
||||
|
|
@ -174,7 +181,7 @@ export default class RightSidebarMenu extends React.Component {
|
|||
/>
|
||||
</Item>
|
||||
<Divider/>
|
||||
<Item onPress={this.handlePress}>
|
||||
<Item onPress={this.props.actions.logout}>
|
||||
<Icon
|
||||
style={Styles.icon}
|
||||
name='sign-out'
|
||||
|
|
@ -190,23 +197,6 @@ export default class RightSidebarMenu extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
function Item({onPress, children}) {
|
||||
return (
|
||||
<TouchableHighlight
|
||||
underlayColor='rgba(255, 255, 255, 0.3)'
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={Styles.item}>
|
||||
{children}
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
Item.propTypes = {
|
||||
onPress: React.PropTypes.func.isRequired,
|
||||
children: React.PropTypes.node
|
||||
};
|
||||
|
||||
function Divider() {
|
||||
return <View style={Styles.divider}/>;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {goToFlaggedPosts, goToRecentMentions} from 'actions/navigation';
|
||||
import {logout} from 'actions/users';
|
||||
|
||||
import RightSidebarMenu from './right_sidebar_menu';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return ownProps;
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToFlaggedPosts,
|
||||
goToRecentMentions,
|
||||
logout
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(RightSidebarMenu);
|
||||
|
|
@ -8,6 +8,8 @@ import GeneralTypes from './general';
|
|||
import UsersTypes from './users';
|
||||
import TeamsTypes from './teams';
|
||||
import PostsTypes from './posts';
|
||||
import NavigationTypes from './navigation';
|
||||
|
||||
import RequestStatus from './request_status';
|
||||
|
||||
export {
|
||||
|
|
@ -18,5 +20,7 @@ export {
|
|||
TeamsTypes,
|
||||
ChannelTypes,
|
||||
PostsTypes,
|
||||
NavigationTypes,
|
||||
|
||||
RequestStatus
|
||||
};
|
||||
|
|
|
|||
14
src/constants/navigation.js
Normal file
14
src/constants/navigation.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import keymirror from 'keymirror';
|
||||
|
||||
const NavigationTypes = keymirror({
|
||||
NAVIGATION_PUSH: null,
|
||||
NAVIGATION_POP: null,
|
||||
NAVIGATION_JUMP: null,
|
||||
NAVIGATION_JUMP_TO_INDEX: null,
|
||||
NAVIGATION_RESET: null
|
||||
});
|
||||
|
||||
export default NavigationTypes;
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Config from 'config';
|
||||
import {connect} from 'react-redux';
|
||||
import {loadDevice} from 'actions/device';
|
||||
import Loading from 'components/loading';
|
||||
import Routes from 'routes';
|
||||
|
||||
import {getTranslations} from 'i18n';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
|
||||
class RootContainer extends React.Component {
|
||||
static propTypes = {
|
||||
device: React.PropTypes.object.isRequired,
|
||||
loadDevice: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.props.loadDevice();
|
||||
}
|
||||
|
||||
render() {
|
||||
const device = this.props.device;
|
||||
|
||||
if (device.loading) {
|
||||
return (
|
||||
<Loading/>
|
||||
);
|
||||
}
|
||||
|
||||
const locale = Config.DefaultLocale;
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
key={locale}
|
||||
locale={locale}
|
||||
messages={getTranslations(locale)}
|
||||
>
|
||||
<Routes/>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
device: state.views.device
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
loadDevice
|
||||
})(RootContainer);
|
||||
27
src/layouts/root_layout/root_layout.js
Normal file
27
src/layouts/root_layout/root_layout.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {getTranslations} from 'i18n';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
|
||||
export default class RootLayout extends React.Component {
|
||||
static propTypes = {
|
||||
children: React.PropTypes.node,
|
||||
locale: React.PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
const locale = this.props.locale;
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={locale}
|
||||
messages={getTranslations(locale)}
|
||||
>
|
||||
{this.props.children}
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
25
src/layouts/root_layout/root_layout_container.js
Normal file
25
src/layouts/root_layout/root_layout_container.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import Config from 'config';
|
||||
|
||||
import RootLayout from './root_layout';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const users = state.entities.users;
|
||||
const currentUserId = users.currentId;
|
||||
|
||||
let locale = Config.DefaultLocale;
|
||||
if (currentUserId && users.profiles[currentUserId]) {
|
||||
locale = users.profiles[currentUserId].locale;
|
||||
}
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
locale
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(RootLayout);
|
||||
|
|
@ -5,13 +5,17 @@ import React from 'react';
|
|||
import {Provider} from 'react-redux';
|
||||
|
||||
import store from 'store';
|
||||
import RootContainer from 'containers/root_container.js';
|
||||
|
||||
import Router from 'navigation/router';
|
||||
import RootLayout from 'layouts/root_layout/root_layout_container';
|
||||
|
||||
export default class Mattermost extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<RootContainer/>
|
||||
<RootLayout>
|
||||
<Router/>
|
||||
</RootLayout>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
122
src/navigation/router.js
Normal file
122
src/navigation/router.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {goBack} from 'actions/navigation';
|
||||
|
||||
import {getComponentForScene} from 'scenes';
|
||||
|
||||
import {Easing, NavigationExperimental, View} from 'react-native';
|
||||
import FormattedText from 'components/formatted_text.js';
|
||||
|
||||
class Router extends React.Component {
|
||||
static propTypes = {
|
||||
navigation: React.PropTypes.object,
|
||||
actions: React.PropTypes.shape({
|
||||
goBack: React.PropTypes.func
|
||||
}).isRequired
|
||||
}
|
||||
|
||||
renderTransition = (transitionProps) => {
|
||||
let title;
|
||||
if (transitionProps.scene.route.title) {
|
||||
title = (
|
||||
<NavigationExperimental.Header
|
||||
{...transitionProps}
|
||||
onNavigateBack={this.props.actions.goBack}
|
||||
renderTitleComponent={this.renderTitle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const renderedScenes = transitionProps.scenes.map((scene) => {
|
||||
const cardProps = {
|
||||
...transitionProps,
|
||||
scene
|
||||
};
|
||||
|
||||
return (
|
||||
<NavigationExperimental.Card
|
||||
{...cardProps}
|
||||
style={NavigationExperimental.Card.PagerStyleInterpolator.forHorizontal({
|
||||
...cardProps,
|
||||
onNavigateBack: this.props.actions.goBack
|
||||
})}
|
||||
onNavigateBack={this.props.actions.goBack}
|
||||
renderScene={this.renderScene}
|
||||
key={scene.key}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={{flex: 1, flexDirection: 'column-reverse'}}>
|
||||
<View style={{flex: 1}}>
|
||||
{renderedScenes}
|
||||
</View>
|
||||
{title}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderTitle = ({scene}) => {
|
||||
const title = scene.route.title;
|
||||
|
||||
if (!title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationExperimental.Header.Title>
|
||||
<FormattedText
|
||||
id={title.id}
|
||||
defaultMessage={title.defaultMessage}
|
||||
/>
|
||||
</NavigationExperimental.Header.Title>
|
||||
);
|
||||
}
|
||||
|
||||
renderScene = ({scene}) => {
|
||||
const SceneComponent = getComponentForScene(scene.route.key);
|
||||
|
||||
return <SceneComponent {...scene.route.props}/>;
|
||||
}
|
||||
|
||||
configureTransition = () => {
|
||||
return {
|
||||
duration: 500,
|
||||
easing: Easing.inOut(Easing.ease)
|
||||
};
|
||||
}
|
||||
|
||||
render = () => {
|
||||
return (
|
||||
<NavigationExperimental.Transitioner
|
||||
style={{flex: 1}}
|
||||
navigationState={this.props.navigation}
|
||||
render={this.renderTransition}
|
||||
configureTransition={this.configureTransition}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
navigation: state.navigation
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goBack
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Router);
|
||||
26
src/navigation/routes.js
Normal file
26
src/navigation/routes.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
export default {
|
||||
Root: {
|
||||
key: 'Root'
|
||||
},
|
||||
SelectServer: {
|
||||
key: 'SelectServer',
|
||||
title: {id: 'mobile.routes.enterServerUrl', defaultMessage: 'Enter Server URL'}
|
||||
},
|
||||
Login: {
|
||||
key: 'Login',
|
||||
title: {id: 'mobile.routes.login', defaultMessage: 'Login'}
|
||||
},
|
||||
SelectTeam: {
|
||||
key: 'SelectTeam',
|
||||
title: {id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}
|
||||
},
|
||||
ChannelView: {
|
||||
key: 'ChannelView'
|
||||
},
|
||||
Search: {
|
||||
key: 'Search'
|
||||
}
|
||||
};
|
||||
|
|
@ -6,9 +6,11 @@ import {combineReducers} from 'redux';
|
|||
import entities from './entities';
|
||||
import requests from './requests';
|
||||
import views from './views';
|
||||
import navigation from './navigation';
|
||||
|
||||
export default combineReducers({
|
||||
entities,
|
||||
requests,
|
||||
views
|
||||
views,
|
||||
navigation
|
||||
});
|
||||
|
|
|
|||
40
src/reducers/navigation/index.js
Normal file
40
src/reducers/navigation/index.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {NavigationExperimental} from 'react-native';
|
||||
|
||||
import {UsersTypes, NavigationTypes} from 'constants';
|
||||
|
||||
import Routes from 'navigation/routes.js';
|
||||
|
||||
const initialState = {
|
||||
index: 0,
|
||||
routes: [
|
||||
Routes.Root
|
||||
]
|
||||
};
|
||||
|
||||
export default function(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case NavigationTypes.NAVIGATION_PUSH:
|
||||
return NavigationExperimental.StateUtils.push(state, action.route);
|
||||
|
||||
case NavigationTypes.NAVIGATION_POP:
|
||||
return NavigationExperimental.StateUtils.pop(state);
|
||||
|
||||
case NavigationTypes.NAVIGATION_JUMP:
|
||||
return NavigationExperimental.StateUtils.jumpTo(state, action.key);
|
||||
|
||||
case NavigationTypes.NAVIGATION_JUMP_TO_INDEX:
|
||||
return NavigationExperimental.StateUtils.jumpToIndex(state, action.index);
|
||||
|
||||
case NavigationTypes.NAVIGATION_RESET:
|
||||
return NavigationExperimental.StateUtils.reset(state, action.routes, action.index);
|
||||
|
||||
case UsersTypes.LOGOUT_SUCCESS:
|
||||
return NavigationExperimental.StateUtils.reset(state, initialState.routes, initialState.index);
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import {Scene, Router} from 'react-native-router-flux';
|
||||
|
||||
import LoginContainer from './login/login_container.js';
|
||||
import SelectServerContainer from './select_server/select_server_container.js';
|
||||
import SelectTeamContainer from './select_team/select_team_container.js';
|
||||
import ChannelContainer from './channel/channel_container.js';
|
||||
import Logout from 'components/logout';
|
||||
import {logout} from 'actions/users';
|
||||
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
class Routes extends Component {
|
||||
static propTypes = {
|
||||
intl: intlShape.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<Scene key='root'>
|
||||
<Scene
|
||||
key='goToSelectServer'
|
||||
component={SelectServerContainer}
|
||||
title={formatMessage({id: 'mobile.routes.enterServerUrl', defaultMessage: 'Enter Server URL'})}
|
||||
initial={true}
|
||||
/>
|
||||
<Scene
|
||||
key='goToLogin'
|
||||
component={LoginContainer}
|
||||
title={formatMessage({id: 'mobile.routes.login', defaultMessage: 'Login'})}
|
||||
/>
|
||||
<Scene
|
||||
key='goToSelectTeam'
|
||||
component={SelectTeamContainer}
|
||||
title={formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'})}
|
||||
renderRightButton={() =>
|
||||
<Logout actions={logout}/>
|
||||
}
|
||||
/>
|
||||
<Scene
|
||||
key='goToMain'
|
||||
component={ChannelContainer}
|
||||
hideNavBar={true}
|
||||
/>
|
||||
</Scene>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(Routes);
|
||||
|
|
@ -6,9 +6,9 @@ import React from 'react';
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
import Drawer from 'react-native-drawer';
|
||||
import ChannelSidebar from 'components/channel_sidebar.js';
|
||||
import Loading from 'components/loading.js';
|
||||
import RightSidebarMenu from 'components/right_sidebar_menu.js';
|
||||
import ChannelSidebar from 'components/channel_sidebar';
|
||||
import Loading from 'components/loading';
|
||||
import RightSidebarMenu from 'components/right_sidebar_menu';
|
||||
import {StatusBar, Text, TouchableHighlight, View} from 'react-native';
|
||||
|
||||
export default class Channel extends React.Component {
|
||||
|
|
@ -81,13 +81,13 @@ export default class Channel extends React.Component {
|
|||
<Drawer
|
||||
open={this.state.rightSidebarOpen}
|
||||
type='displace'
|
||||
content={<RightSidebarMenu/>}
|
||||
content={<RightSidebarMenu onClose={this.closeRightSidebar}/>}
|
||||
side='right'
|
||||
tapToClose={true}
|
||||
onCloseStart={this.closeRightSidebar}
|
||||
openDrawerOffset={0.2}
|
||||
>
|
||||
<View style={{flex: 1, flexDirection: 'row', justifyContent: 'space-between', paddingTop: 20}}>
|
||||
<View style={{backgroundColor: 'skyblue', flex: 1, flexDirection: 'row', justifyContent: 'space-between', marginTop: 20}}>
|
||||
<TouchableHighlight
|
||||
onPress={this.openLeftSidebar}
|
||||
style={{height: 50, width: 50}}
|
||||
22
src/scenes/index.js
Normal file
22
src/scenes/index.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import Channel from './channel/channel_container.js';
|
||||
import Login from './login/login_container.js';
|
||||
import Root from './root/root_container.js';
|
||||
import Search from './search/search_container.js';
|
||||
import SelectServer from './select_server/select_server_container.js';
|
||||
import SelectTeam from './select_team/select_team_container.js';
|
||||
|
||||
const scenes = {
|
||||
Root,
|
||||
SelectServer,
|
||||
Login,
|
||||
SelectTeam,
|
||||
ChannelView: Channel, // Special case the name for this one to avoid ambiguity
|
||||
Search
|
||||
};
|
||||
|
||||
export function getComponentForScene(key) {
|
||||
return scenes[key];
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import {View, Text, TextInput, Image} from 'react-native';
|
||||
import {Actions as Routes} from 'react-native-router-flux';
|
||||
|
||||
import Button from 'components/button';
|
||||
import FormattedText from 'components/formatted_text';
|
||||
|
|
@ -36,7 +35,7 @@ class Login extends Component {
|
|||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.loginRequest.status === RequestStatus.STARTED && nextProps.loginRequest.status === RequestStatus.SUCCESS) {
|
||||
Routes.goToSelectTeam();
|
||||
this.props.actions.goToSelectTeam();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6,6 +6,7 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {getClientConfig, getLicenseConfig} from 'actions/general';
|
||||
import * as LoginActions from 'actions/views/login';
|
||||
import {goToSelectTeam} from 'actions/navigation';
|
||||
import {login} from 'actions/users';
|
||||
|
||||
import Login from './login.js';
|
||||
|
|
@ -27,7 +28,8 @@ function mapDispatchToProps(dispatch) {
|
|||
...LoginActions,
|
||||
login,
|
||||
getClientConfig,
|
||||
getLicenseConfig
|
||||
getLicenseConfig,
|
||||
goToSelectTeam
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
20
src/scenes/root/root.js
Normal file
20
src/scenes/root/root.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default class Root extends React.Component {
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
// Any initialization logic for navigation, setting up the client, etc should go here
|
||||
|
||||
this.props.actions.goToSelectServer();
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
23
src/scenes/root/root_container.js
Normal file
23
src/scenes/root/root_container.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {goToSelectServer} from 'actions/views/root';
|
||||
|
||||
import Root from './root';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return ownProps;
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToSelectServer
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Root);
|
||||
30
src/scenes/search/search.js
Normal file
30
src/scenes/search/search.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Button from 'components/button';
|
||||
import {GlobalStyles} from 'styles';
|
||||
import {Text, View} from 'react-native';
|
||||
|
||||
export default class Search extends React.Component {
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
searchType: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={GlobalStyles.container}>
|
||||
<Text>
|
||||
{'This is a search for ' + this.props.searchType}
|
||||
</Text>
|
||||
<Button onPress={this.props.actions.goBack}>
|
||||
<Text>
|
||||
{'<< Go back'}
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
26
src/scenes/search/search_container.js
Normal file
26
src/scenes/search/search_container.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {goBack} from 'actions/navigation';
|
||||
|
||||
import Search from './search';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
...state.views.search
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goBack
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Search);
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
import React, {Component} from 'react';
|
||||
import {View, TextInput, Image} from 'react-native';
|
||||
import KeyboardSpacer from 'react-native-keyboard-spacer';
|
||||
import {Actions as Routes} from 'react-native-router-flux';
|
||||
|
||||
import Client from 'client';
|
||||
import Button from 'components/button';
|
||||
|
|
@ -30,7 +29,7 @@ class SelectServer extends Component {
|
|||
|
||||
this.props.actions.getPing().then(() => {
|
||||
if (this.props.server.status === RequestStatus.SUCCESS) {
|
||||
Routes.goToLogin();
|
||||
this.props.actions.goToLogin();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -5,6 +5,7 @@ import {bindActionCreators} from 'redux';
|
|||
import {connect} from 'react-redux';
|
||||
|
||||
import {getPing} from 'actions/general';
|
||||
import {goToLogin} from 'actions/navigation';
|
||||
import * as SelectServerActions from 'actions/views/select_server';
|
||||
|
||||
import SelectServer from './select_server';
|
||||
|
|
@ -20,7 +21,8 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
...SelectServerActions,
|
||||
getPing
|
||||
getPing,
|
||||
goToLogin
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
import React, {Component, PropTypes} from 'react';
|
||||
|
||||
import {View, Image, Text} from 'react-native';
|
||||
import {Actions as Routes} from 'react-native-router-flux';
|
||||
import Button from 'react-native-button';
|
||||
import Loading from 'components/loading';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
|
@ -30,7 +29,7 @@ export default class SelectTeam extends Component {
|
|||
|
||||
onSelectTeam(team) {
|
||||
this.props.actions.selectTeam(team);
|
||||
Routes.goToMain({currentTeamId: team.id});
|
||||
this.props.actions.goToChannelView();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -3,7 +3,10 @@
|
|||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import * as teamActions from 'actions/teams';
|
||||
import {goToChannelView} from 'actions/navigation';
|
||||
|
||||
import SelectTeamView from './select_team.js';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
@ -17,7 +20,10 @@ function mapStateToProps(state) {
|
|||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(teamActions, dispatch)
|
||||
actions: bindActionCreators({
|
||||
...teamActions,
|
||||
goToChannelView
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -7,6 +7,7 @@ import * as Actions from 'actions/users';
|
|||
import Client from 'client';
|
||||
import configureStore from 'store/configureStore';
|
||||
import {RequestStatus} from 'constants';
|
||||
import Routes from 'navigation/routes';
|
||||
import TestHelper from 'test_helper';
|
||||
|
||||
describe('Actions.Users', () => {
|
||||
|
|
@ -54,13 +55,15 @@ describe('Actions.Users', () => {
|
|||
const store = configureStore();
|
||||
|
||||
store.subscribe(() => {
|
||||
const logoutRequest = store.getState().requests.users.logout;
|
||||
const general = store.getState().entities.general;
|
||||
const users = store.getState().entities.users;
|
||||
const loginView = store.getState().views.login;
|
||||
const teams = store.getState().entities.teams;
|
||||
const channels = store.getState().entities.channels;
|
||||
const posts = store.getState().entities.posts;
|
||||
const state = store.getState();
|
||||
const logoutRequest = state.requests.users.logout;
|
||||
const general = state.entities.general;
|
||||
const users = state.entities.users;
|
||||
const loginView = state.views.login;
|
||||
const teams = state.entities.teams;
|
||||
const channels = state.entities.channels;
|
||||
const posts = state.entities.posts;
|
||||
const navigation = state.navigation;
|
||||
|
||||
if (logoutRequest.status === RequestStatus.SUCCESS || logoutRequest.status === RequestStatus.FAILURE) {
|
||||
if (logoutRequest.error) {
|
||||
|
|
@ -95,6 +98,8 @@ describe('Actions.Users', () => {
|
|||
assert.strictEqual(posts.currentFocusedPostId, '', 'current focused post id is not empty');
|
||||
assert.deepStrictEqual(posts.postsInfo, {}, 'posts info is not empty');
|
||||
assert.deepStrictEqual(posts.latestPageTime, {}, 'posts latest page time is not empty');
|
||||
assert.strictEqual(navigation.index, 0, 'navigation not reset to first element of stack');
|
||||
assert.deepStrictEqual(navigation.routes, [Routes.Root], 'navigation not reset to root route');
|
||||
|
||||
done();
|
||||
}
|
||||
|
|
|
|||
223
test/reducers/navigation.test.js
Normal file
223
test/reducers/navigation.test.js
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import assert from 'assert';
|
||||
import deepFreeze from 'deep-freeze';
|
||||
|
||||
import {NavigationTypes} from 'constants';
|
||||
import Routes from 'navigation/routes';
|
||||
import reduceNavigation from 'reducers/navigation';
|
||||
|
||||
function reduceAndFreeze(state, actions) {
|
||||
return deepFreeze(reduceNavigation(state, actions));
|
||||
}
|
||||
|
||||
function initialState() {
|
||||
return reduceAndFreeze(undefined, {});
|
||||
}
|
||||
|
||||
describe('Reducers.Navigation', () => {
|
||||
it('initial state', () => {
|
||||
const state = initialState();
|
||||
|
||||
assert.deepEqual(state, {
|
||||
index: 0,
|
||||
routes: [Routes.Root]
|
||||
}, 'initial state');
|
||||
});
|
||||
|
||||
it('NAVIGATION_PUSH', () => {
|
||||
let state = initialState();
|
||||
|
||||
it('first push', () => {
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.PUSH,
|
||||
route: {key: 'fake'}
|
||||
});
|
||||
|
||||
assert.deepEqual(state, {
|
||||
index: 1,
|
||||
routes: [Routes.Root, {key: 'fake'}]
|
||||
});
|
||||
});
|
||||
|
||||
it('second push', () => {
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.PUSH,
|
||||
route: {key: 'fake2'}
|
||||
});
|
||||
|
||||
assert.deepEqual(state, {
|
||||
index: 1,
|
||||
routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}]
|
||||
});
|
||||
});
|
||||
|
||||
it('push when not at head of stack', () => {
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.JUMP,
|
||||
key: Routes.key.root
|
||||
});
|
||||
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.PUSH,
|
||||
route: {key: 'fake3'}
|
||||
});
|
||||
|
||||
// The next route on the stack is replaced, but anything past that is left unchanged
|
||||
assert.deepEqual(state, {
|
||||
index: 1,
|
||||
routes: [Routes.Root, {key: 'fake3'}, {key: 'fake2'}]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('NAVIGATION_POP', () => {
|
||||
let state = initialState();
|
||||
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.PUSH,
|
||||
route: {key: 'fake'}
|
||||
});
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.PUSH,
|
||||
route: {key: 'fake2'}
|
||||
});
|
||||
|
||||
it('first pop', () => {
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.POP
|
||||
});
|
||||
|
||||
assert.deepEqual(state, {
|
||||
index: 1,
|
||||
routes: [Routes.Root, {key: 'fake'}]
|
||||
});
|
||||
});
|
||||
|
||||
it('second pop', () => {
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.POP
|
||||
});
|
||||
|
||||
assert.deepEqual(state, {
|
||||
index: 0,
|
||||
routes: [Routes.Root]
|
||||
});
|
||||
});
|
||||
|
||||
it('pop last entry on stack', () => {
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.POP
|
||||
});
|
||||
|
||||
assert.deepEqual(state, {
|
||||
index: 0,
|
||||
routes: [Routes.Root]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('NAVIGATION_JUMP', () => {
|
||||
let state = initialState();
|
||||
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.PUSH,
|
||||
route: {key: 'fake'}
|
||||
});
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.PUSH,
|
||||
route: {key: 'fake2'}
|
||||
});
|
||||
|
||||
it('jump back', () => {
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.NAVIGATION_JUMP,
|
||||
key: Routes.Root.key
|
||||
});
|
||||
|
||||
// The rest of the stack is preserved
|
||||
assert.deepEqual(state, {
|
||||
index: 0,
|
||||
routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}]
|
||||
});
|
||||
});
|
||||
|
||||
it('jump forward', () => {
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.NAVIGATION_JUMP,
|
||||
key: 'fake'
|
||||
});
|
||||
|
||||
assert.deepEqual(state, {
|
||||
index: 2,
|
||||
routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('NAVIGATION_JUMP_TO_INDEX', () => {
|
||||
let state = initialState();
|
||||
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.PUSH,
|
||||
route: {key: 'fake'}
|
||||
});
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.PUSH,
|
||||
route: {key: 'fake2'}
|
||||
});
|
||||
|
||||
it('jump back', () => {
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.NAVIGATION_JUMP_TO_INDEX,
|
||||
index: 1
|
||||
});
|
||||
|
||||
// The rest of the stack is preserved
|
||||
assert.deepEqual(state, {
|
||||
index: 0,
|
||||
routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}]
|
||||
});
|
||||
});
|
||||
|
||||
it('jump forward', () => {
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.NAVIGATION_JUMP_TO_INDEX,
|
||||
index: 2
|
||||
});
|
||||
|
||||
assert.deepEqual(state, {
|
||||
index: 2,
|
||||
routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('NAVIGATION_RESET', () => {
|
||||
let state = initialState();
|
||||
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.PUSH,
|
||||
route: {key: 'fake'}
|
||||
});
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.PUSH,
|
||||
route: {key: 'fake2'}
|
||||
});
|
||||
|
||||
it('reset', () => {
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.NAVIGATION_RESET,
|
||||
index: 1,
|
||||
routes: [{key: 'fake3'}, {key: 'fake4'}]
|
||||
});
|
||||
|
||||
// The navigation state is wiped out and replaced with the new state
|
||||
assert.deepEqual(state, {
|
||||
index: 1,
|
||||
routes: [{key: 'fake3'}, {key: 'fake4'}]
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue