Copy native support to @react-navigation/native

This commit is contained in:
Eric Vicenti
2018-10-11 20:47:20 -07:00
parent b331dea1a1
commit df1728e13b
17 changed files with 70 additions and 1143 deletions

View File

@@ -515,6 +515,25 @@
path-to-regexp "^2.4.0"
query-string "^6.2.0"
"@react-navigation/core@^3.0.0-alpha.4":
version "3.0.0-alpha.4"
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-3.0.0-alpha.4.tgz#0167a8352e867ebd09e444aac42b315f569fb81d"
integrity sha512-kj9PzNadCjPYU1NGzNrg6eb3KDTNECUzWL38Ef7yHwhNG6U62AbedLD8VtuSZHGAEFXnI68hhnuoIe+9f6aJ8w==
dependencies:
create-react-context "^0.2.3"
hoist-non-react-statics "^3.0.1"
path-to-regexp "^2.4.0"
query-string "^6.2.0"
"@react-navigation/native@^3.0.0-alpha.2":
version "3.0.0-alpha.2"
resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-3.0.0-alpha.2.tgz#c483a54ddb82768c0c2c6923a8007ac9c924b92d"
integrity sha512-6bPR+g15gJQo5bER4oJ4lxTs0hk0rZOnrgMwI+MflAqlRwqUBseRHiBr5tvfssrkTu3Q2GqZ0eSucc6WQDBU/A==
dependencies:
"@react-navigation/core" "^3.0.0-alpha.4"
hoist-non-react-statics "^3.0.1"
react-lifecycles-compat "^3.0.4"
abab@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"

View File

@@ -31,6 +31,7 @@
},
"dependencies": {
"@react-navigation/core": "^3.0.0-alpha.2",
"@react-navigation/native": "^3.0.0-alpha.2",
"react-lifecycles-compat": "^3",
"react-native-gesture-handler": "^1.0.0",
"react-native-safe-area-view": "0.11.0",
@@ -89,7 +90,7 @@
"<rootDir>/examples/"
],
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation-deprecated-tab-navigator|react-navigation-stack|@react-navigation/core)"
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation-deprecated-tab-navigator|react-navigation-stack|@react-navigation/core|@react-navigation/native)"
]
},
"lint-staged": {

View File

@@ -1,277 +0,0 @@
import React from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
import createNavigationContainer, {
_TESTING_ONLY_reset_container_count,
} from '../createNavigationContainer';
import {
NavigationActions,
createNavigator,
StackRouter,
SwitchView,
} from '@react-navigation/core';
function createStackNavigator(routeConfigMap, stackConfig = {}) {
const router = StackRouter(routeConfigMap, stackConfig);
return createNavigationContainer(
createNavigator(SwitchView, router, stackConfig)
);
}
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
.create(<NavigationContainer />)
.getInstance();
expect(navigationContainer.state.nav).toMatchObject({ index: 0 });
expect(navigationContainer.state.nav.routes).toBeInstanceOf(Array);
expect(navigationContainer.state.nav.routes.length).toBe(1);
expect(navigationContainer.state.nav.routes[0]).toMatchObject({
routeName: 'foo',
});
});
});
describe('dispatch', () => {
it('returns true when given a valid action', () => {
const navigationContainer = renderer
.create(<NavigationContainer />)
.getInstance();
jest.runOnlyPendingTimers();
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'bar' })
)
).toEqual(true);
});
it('returns false when given an invalid action', () => {
const navigationContainer = renderer
.create(<NavigationContainer />)
.getInstance();
jest.runOnlyPendingTimers();
expect(navigationContainer.dispatch(NavigationActions.back())).toEqual(
false
);
});
it('updates state.nav with an action by the next tick', () => {
const navigationContainer = renderer
.create(<NavigationContainer />)
.getInstance();
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'bar' })
)
).toEqual(true);
// Fake the passing of a tick
jest.runOnlyPendingTimers();
expect(navigationContainer.state.nav).toMatchObject({
index: 1,
routes: [{ routeName: 'foo' }, { routeName: 'bar' }],
});
});
it('does not discard actions when called twice in one tick', () => {
const navigationContainer = renderer
.create(<NavigationContainer />)
.getInstance();
const initialState = JSON.parse(
JSON.stringify(navigationContainer.state.nav)
);
// First dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'bar' })
)
).toEqual(true);
// Make sure that the test runner has NOT synchronously applied setState before the tick
expect(navigationContainer.state.nav).toMatchObject(initialState);
// Second dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'baz' })
)
).toEqual(true);
// Fake the passing of a tick
jest.runOnlyPendingTimers();
expect(navigationContainer.state.nav).toMatchObject({
index: 2,
routes: [
{ routeName: 'foo' },
{ routeName: 'bar' },
{ routeName: 'baz' },
],
});
});
it('does not discard actions when called more than 2 times in one tick', () => {
const navigationContainer = renderer
.create(<NavigationContainer />)
.getInstance();
const initialState = JSON.parse(
JSON.stringify(navigationContainer.state.nav)
);
// First dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'bar' })
)
).toEqual(true);
// Make sure that the test runner has NOT synchronously applied setState before the tick
expect(navigationContainer.state.nav).toMatchObject(initialState);
// Second dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'baz' })
)
).toEqual(true);
// Third dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'car' })
)
).toEqual(true);
// Fourth dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'dog' })
)
).toEqual(true);
// Fifth dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'elk' })
)
).toEqual(true);
// Fake the passing of a tick
jest.runOnlyPendingTimers();
expect(navigationContainer.state.nav).toMatchObject({
index: 5,
routes: [
{ routeName: 'foo' },
{ routeName: 'bar' },
{ routeName: 'baz' },
{ routeName: 'car' },
{ routeName: 'dog' },
{ routeName: 'elk' },
],
});
});
});
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

@@ -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

@@ -1,385 +0,0 @@
import React from 'react';
import { AsyncStorage, Linking, Platform, BackHandler } from 'react-native';
import { polyfill } from 'react-lifecycles-compat';
import {
NavigationActions,
pathUtils,
getNavigation,
} from '@react-navigation/core';
import invariant from './utils/invariant';
import docsUrl from './utils/docsUrl';
const { urlToPathAndParams } = pathUtils;
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
* in case it's not passed from above.
* This allows to use e.g. the StackNavigator and TabNavigator as root-level
* components.
*/
export default function createNavigationContainer(Component) {
class NavigationContainer extends React.Component {
subs = null;
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._initialAction = NavigationActions.init();
if (this._isStateful()) {
this.subs = BackHandler.addEventListener('hardwareBackPress', () => {
if (!this._isMounted) {
this.subs && this.subs.remove();
} else {
// dispatch returns true if the action results in a state change,
// and false otherwise. This maps well to what BackHandler expects
// from a callback -- true if handled, false if not handled
return this.dispatch(NavigationActions.back());
}
});
}
this.state = {
nav:
this._isStateful() && !props.persistenceKey
? Component.router.getStateForAction(this._initialAction)
: null,
};
}
_renderLoading() {
return this.props.renderLoadingExperimental
? this.props.renderLoadingExperimental()
: null;
}
_isStateful() {
return isStateful(this.props);
}
_validateProps(props) {
if (this._isStateful()) {
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.'
);
}
}
_handleOpenURL = ({ url }) => {
const { enableURLHandling, uriPrefix } = this.props;
if (enableURLHandling === false) {
return;
}
const parsedUrl = urlToPathAndParams(url, uriPrefix);
if (parsedUrl) {
const { path, params } = parsedUrl;
const action = Component.router.getActionForPathAndParams(path, params);
if (action) {
this.dispatch(action);
}
}
};
_onNavigationStateChange(prevNav, nav, action) {
if (
typeof this.props.onNavigationStateChange === 'undefined' &&
this._isStateful() &&
!!process.env.REACT_NAV_LOGGING
) {
/* eslint-disable no-console */
if (console.group) {
console.group('Navigation Dispatch: ');
console.log('Action: ', action);
console.log('New State: ', nav);
console.log('Last State: ', prevNav);
console.groupEnd();
} else {
console.log('Navigation Dispatch: ', {
action,
newState: nav,
lastState: prevNav,
});
}
/* eslint-enable no-console */
return;
}
if (typeof this.props.onNavigationStateChange === 'function') {
this.props.onNavigationStateChange(prevNav, nav, action);
}
}
componentDidUpdate() {
// Clear cached _navState every tick
if (this._navState === this.state.nav) {
this._navState = null;
}
}
async 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, uriPrefix, enableURLHandling } = this.props;
let parsedUrl = null;
let startupStateJSON = null;
if (enableURLHandling !== false) {
startupStateJSON =
persistenceKey && (await AsyncStorage.getItem(persistenceKey));
const url = await Linking.getInitialURL();
parsedUrl = url && urlToPathAndParams(url, uriPrefix);
}
// 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();
});
}
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);
}
// 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;
const dispatchActionEvents = () => {
this._actionEventSubscribers.forEach(subscriber =>
subscriber({
type: 'action',
action,
state: navState,
lastState: lastNavState,
})
);
};
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) {
// 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);
dispatchActionEvents();
this._persistNavigationState(navState);
});
return true;
}
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
);
}
navigation = this._navigation;
}
invariant(navigation, 'failed to get navigation');
return <Component {...this.props} navigation={navigation} />;
}
}
return polyfill(NavigationContainer);
}

View File

@@ -1,4 +1,4 @@
import createNavigationContainer from '../createNavigationContainer';
import { createNavigationContainer } from '@react-navigation/native';
import { createSwitchNavigator } from '@react-navigation/core';
const SwitchNavigator = (routeConfigs, config = {}) => {

View File

@@ -1,57 +0,0 @@
import React from 'react';
import { TextInput } from 'react-native';
export default (Navigator, navigatorConfig) =>
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);
}
}
const onTransitionStart =
this.props.onTransitionStart || navigatorConfig.onTransitionStart;
onTransitionStart &&
onTransitionStart(transitionProps, prevTransitionProps);
};
};

View File

@@ -3,8 +3,9 @@
module.exports = {
// Core
get createNavigationContainer() {
return require('./createNavigationContainer').default;
return require('@react-navigation/native').createNavigationContainer;
},
get StateUtils() {
return require('@react-navigation/core').StateUtils;
},
@@ -17,7 +18,7 @@ module.exports = {
return require('@react-navigation/core').createNavigator;
},
get createKeyboardAwareNavigator() {
return require('./navigators/createKeyboardAwareNavigator').default;
return require('@react-navigation/native').createKeyboardAwareNavigator;
},
get NavigationProvider() {
return require('@react-navigation/core').NavigationProvider;
@@ -135,7 +136,7 @@ module.exports = {
return require('@react-navigation/core').SceneView;
},
get ResourceSavingSceneView() {
return require('./views/ResourceSavingSceneView').default;
return require('@react-navigation/native').ResourceSavingSceneView;
},
// Header
@@ -201,6 +202,6 @@ module.exports = {
return require('@react-navigation/core').withNavigationFocus;
},
get withOrientation() {
return require('./views/withOrientation').default;
return require('@react-navigation/native').withOrientation;
},
};

View File

@@ -1,47 +0,0 @@
/**
* Use invariant() to assert state which your program assumes to be true.
*
* Provide sprintf-style format (only %s is supported) and arguments
* to provide information about what broke and what you were
* expecting.
*
* The invariant message will be stripped in production, but the invariant
* will remain to ensure logic does not differ in production.
*/
var validateFormat = function(format) {};
if (__DEV__) {
validateFormat = function(format) {
if (format === undefined) {
throw new Error('invariant requires an error message argument');
}
};
}
function invariant(condition, format, a, b, c, d, e, f) {
validateFormat(format);
if (!condition) {
var error;
if (format === undefined) {
error = new Error(
'Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.'
);
} else {
var args = [a, b, c, d, e, f];
var argIndex = 0;
error = new Error(
format.replace(/%s/g, function() {
return args[argIndex++];
})
);
error.name = 'Invariant Violation';
}
error.framesToPop = 1; // we don't care about invariant's own frame
throw error;
}
}
module.exports = invariant;

View File

@@ -1,79 +0,0 @@
import React from 'react';
import { Platform, StyleSheet, View } from 'react-native';
import { polyfill } from 'react-lifecycles-compat';
import { SceneView } from '@react-navigation/core';
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
class ResourceSavingSceneView extends React.PureComponent {
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.isFocused && !prevState.awake) {
return { awake: true };
} else {
return null;
}
}
constructor(props) {
super();
this.state = {
awake: props.lazy ? props.isFocused : true,
};
}
render() {
const { awake } = this.state;
const {
isFocused,
childNavigation,
navigation,
removeClippedSubviews,
lazy,
...rest
} = this.props;
return (
<View
style={styles.container}
collapsable={false}
removeClippedSubviews={
Platform.OS === 'android'
? removeClippedSubviews
: !isFocused && removeClippedSubviews
}
>
<View
style={
this._mustAlwaysBeVisible() || isFocused
? styles.innerAttached
: styles.innerDetached
}
>
{awake ? <SceneView {...rest} navigation={childNavigation} /> : null}
</View>
</View>
);
}
_mustAlwaysBeVisible = () => {
return this.props.animationEnabled || this.props.swipeEnabled;
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
overflow: 'hidden',
},
innerAttached: {
flex: 1,
},
innerDetached: {
flex: 1,
top: FAR_FAR_AWAY,
},
});
export default polyfill(ResourceSavingSceneView);

View File

@@ -1,6 +0,0 @@
{
"extends": "../../../.eslintrc",
"env": {
"jest": true
},
}

View File

@@ -1,48 +0,0 @@
import React from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
const {
TabView,
TabBarBottom,
} = require('react-navigation-deprecated-tab-navigator');
const dummyEventSubscriber = (name, handler) => ({
remove: () => {},
});
describe('TabBarBottom', () => {
jest.useFakeTimers();
it('renders successfully', () => {
const route = { key: 's1', routeName: 's1' };
const navigation = {
state: {
index: 0,
routes: [route],
},
addListener: dummyEventSubscriber,
};
const rendered = renderer
.create(
<TabView
tabBarComponent={TabBarBottom}
navigation={navigation}
navigationConfig={{}}
descriptors={{
s1: {
state: route,
key: route.key,
options: {},
navigation: { state: route },
getComponent: () => View,
},
}}
/>
)
.toJSON();
expect(rendered).toMatchSnapshot();
});
});

View File

@@ -1,144 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TabBarBottom renders successfully 1`] = `
<View
collapsable={false}
style={
Array [
Object {
"flex": 1,
"overflow": "hidden",
},
Object {
"flex": 1,
},
]
}
>
<View
onLayout={[Function]}
style={
Object {
"flex": 1,
}
}
>
<RCTScrollView
DEPRECATED_sendUpdatedChildFrames={false}
alwaysBounceHorizontal={false}
alwaysBounceVertical={false}
automaticallyAdjustContentInsets={false}
bounces={false}
contentContainerStyle={
Object {
"flex": 1,
}
}
contentOffset={
Object {
"x": 0,
"y": 0,
}
}
directionalLockEnabled={true}
horizontal={true}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="always"
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]}
overScrollMode="never"
pagingEnabled={true}
scrollEnabled={undefined}
scrollEventThrottle={1}
scrollsToTop={false}
sendMomentumEvents={true}
showsHorizontalScrollIndicator={false}
style={
Array [
Object {
"flexDirection": "row",
"flexGrow": 1,
"flexShrink": 1,
"overflow": "scroll",
},
Object {
"flex": 1,
},
]
}
>
<RCTScrollContentView
collapsable={false}
removeClippedSubviews={undefined}
style={
Array [
Object {
"flexDirection": "row",
},
Object {
"flex": 1,
},
]
}
>
<View
style={
Object {
"flex": 1,
"overflow": "hidden",
}
}
testID={undefined}
>
<View
collapsable={false}
removeClippedSubviews={false}
style={
Object {
"flex": 1,
"overflow": "hidden",
}
}
>
<View
style={
Object {
"flex": 1,
}
}
>
<View
navigation={
Object {
"state": Object {
"key": "s1",
"routeName": "s1",
},
}
}
screenProps={undefined}
/>
</View>
</View>
</View>
</RCTScrollContentView>
</RCTScrollView>
</View>
</View>
`;

View File

@@ -1,7 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`it adds isLandscape to props 1`] = `
<View
isLandscape={false}
/>
`;

View File

@@ -1,15 +0,0 @@
import React from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
import withOrientation, { isOrientationLandscape } from '../withOrientation';
test('it adds isLandscape to props', () => {
const WrappedComponent = withOrientation(View);
const rendered = renderer.create(<WrappedComponent />).toJSON();
expect(rendered).toMatchSnapshot();
});
test('calculates orientation correctly', () => {
const isLandscape = isOrientationLandscape({ width: 10, height: 1 });
expect(isLandscape).toBeTruthy();
});

View File

@@ -1,35 +0,0 @@
import React from 'react';
import { Dimensions } from 'react-native';
import hoistNonReactStatic from 'hoist-non-react-statics';
export const isOrientationLandscape = ({ width, height }) => width > height;
export default function(WrappedComponent) {
class withOrientation extends React.Component {
constructor() {
super();
const isLandscape = isOrientationLandscape(Dimensions.get('window'));
this.state = { isLandscape };
}
componentDidMount() {
Dimensions.addEventListener('change', this.handleOrientationChange);
}
componentWillUnmount() {
Dimensions.removeEventListener('change', this.handleOrientationChange);
}
handleOrientationChange = ({ window }) => {
const isLandscape = isOrientationLandscape(window);
this.setState({ isLandscape });
};
render() {
return <WrappedComponent {...this.props} {...this.state} />;
}
}
return hoistNonReactStatic(withOrientation, WrappedComponent);
}

View File

@@ -28,6 +28,25 @@
path-to-regexp "^2.4.0"
query-string "^6.2.0"
"@react-navigation/core@^3.0.0-alpha.4":
version "3.0.0-alpha.4"
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-3.0.0-alpha.4.tgz#0167a8352e867ebd09e444aac42b315f569fb81d"
integrity sha512-kj9PzNadCjPYU1NGzNrg6eb3KDTNECUzWL38Ef7yHwhNG6U62AbedLD8VtuSZHGAEFXnI68hhnuoIe+9f6aJ8w==
dependencies:
create-react-context "^0.2.3"
hoist-non-react-statics "^3.0.1"
path-to-regexp "^2.4.0"
query-string "^6.2.0"
"@react-navigation/native@^3.0.0-alpha.2":
version "3.0.0-alpha.2"
resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-3.0.0-alpha.2.tgz#c483a54ddb82768c0c2c6923a8007ac9c924b92d"
integrity sha512-6bPR+g15gJQo5bER4oJ4lxTs0hk0rZOnrgMwI+MflAqlRwqUBseRHiBr5tvfssrkTu3Q2GqZ0eSucc6WQDBU/A==
dependencies:
"@react-navigation/core" "^3.0.0-alpha.4"
hoist-non-react-statics "^3.0.1"
react-lifecycles-compat "^3.0.4"
abab@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
@@ -1600,9 +1619,9 @@ combined-stream@^1.0.5, combined-stream@~1.0.5, combined-stream@~1.0.6:
delayed-stream "~1.0.0"
commander@^2.11.0, commander@^2.9.0:
version "2.18.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970"
integrity sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
commander@~2.13.0:
version "2.13.0"
@@ -1892,9 +1911,9 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.
ms "2.0.0"
debug@^3.1.0:
version "3.2.5"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.5.tgz#c2418fbfd7a29f4d4f70ff4cea604d4b64c46407"
integrity sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
@@ -2206,9 +2225,9 @@ eslint-plugin-import@^2.7.0:
resolve "^1.6.0"
eslint-plugin-jsx-a11y@^6.0.2:
version "6.1.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.1.1.tgz#7bf56dbe7d47d811d14dbb3ddff644aa656ce8e1"
integrity sha512-JsxNKqa3TwmPypeXNnI75FntkUktGzI1wSa1LgNZdSOMI+B4sxnr1lSF8m8lPiz4mKiC+14ysZQM4scewUrP7A==
version "6.1.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.1.2.tgz#69bca4890b36dcf0fe16dd2129d2d88b98f33f88"
integrity sha512-7gSSmwb3A+fQwtw0arguwMdOdzmKUgnUcbSNlo+GjKLAQFuC2EZxWqG9XHRI8VscBJD5a8raz3RuxQNFW+XJbw==
dependencies:
aria-query "^3.0.0"
array-includes "^3.0.3"
@@ -2531,9 +2550,9 @@ fast-deep-equal@^1.0.0:
integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=
fast-diff@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==
version "1.2.0"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
fast-json-stable-stringify@^2.0.0:
version "2.0.0"
@@ -4590,9 +4609,9 @@ minipass@^2.2.1, minipass@^2.3.3:
yallist "^3.0.0"
minizlib@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb"
integrity sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==
version "1.1.1"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.1.tgz#6734acc045a46e61d596a43bb9d9cd326e19cc42"
integrity sha512-TrfjCjk4jLhcJyGMYymBH6oTXcWjYbUAXTHDbtnWHjZC25h0cdajHuPE1zxb4DVmu8crfh+HwH/WMuyLG0nHBg==
dependencies:
minipass "^2.2.1"
@@ -4781,9 +4800,9 @@ npm-bundled@^1.0.1:
integrity sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==
npm-packlist@^1.1.6:
version "1.1.11"
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.11.tgz#84e8c683cbe7867d34b1d357d893ce29e28a02de"
integrity sha512-CxKlZ24urLkJk+9kCm48RTQ7L4hsmgSVzEk0TLGPzzyuFxD7VNgy5Sl24tOLMzQv773a/NeJ1ce1DKeacqffEA==
version "1.1.12"
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a"
integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g==
dependencies:
ignore-walk "^3.0.1"
npm-bundled "^1.0.1"
@@ -6032,9 +6051,9 @@ schedule@^0.5.0:
object-assign "^4.1.1"
"semver@2 || 3 || 4 || 5", semver@5.x, semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1:
version "5.5.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477"
integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
semver@5.5.0:
version "5.5.0"
@@ -6268,9 +6287,9 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
spdx-correct@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.1.tgz#434434ff9d1726b4d9f4219d1004813d80639e30"
integrity sha512-hxSPZbRZvSDuOvADntOElzJpenIR7wXJkuoUcUtS0erbgt2fgeaoPIYretfKpslMhfFDY4k0MZ2F5CUzhBsSvQ==
version "3.0.2"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.2.tgz#19bb409e91b47b1ad54159243f7312a858db3c2e"
integrity sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ==
dependencies:
spdx-expression-parse "^3.0.0"
spdx-license-ids "^3.0.0"