mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-03-06 22:39:41 +08:00
Add events support to redux example (#3368)
* Fixing redux * Fix tab events and test event nested event subscriptions * Add event support to redux example
This commit is contained in:
committed by
Brent Vatne
parent
50a3f8de93
commit
dca37627a2
@@ -45,35 +45,69 @@ MyHomeScreen.navigationOptions = {
|
||||
),
|
||||
};
|
||||
|
||||
const MyPeopleScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner="People Tab" navigation={navigation} />
|
||||
);
|
||||
class MyPeopleScreen extends React.Component {
|
||||
static navigationOptions = {
|
||||
tabBarLabel: 'People',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-people' : 'ios-people-outline'}
|
||||
size={26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
componentDidMount() {
|
||||
this._s0 = this.props.navigation.addListener('willFocus', this._onEvent);
|
||||
this._s1 = this.props.navigation.addListener('didFocus', this._onEvent);
|
||||
this._s2 = this.props.navigation.addListener('willBlur', this._onEvent);
|
||||
this._s3 = this.props.navigation.addListener('didBlur', this._onEvent);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this._s0.remove();
|
||||
this._s1.remove();
|
||||
this._s2.remove();
|
||||
this._s3.remove();
|
||||
}
|
||||
_onEvent = a => {
|
||||
console.log('EVENT ON PEOPLE TAB', a.type, a);
|
||||
};
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return <MyNavScreen banner="People Tab" navigation={navigation} />;
|
||||
}
|
||||
}
|
||||
|
||||
MyPeopleScreen.navigationOptions = {
|
||||
tabBarLabel: 'People',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-people' : 'ios-people-outline'}
|
||||
size={26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
const MyChatScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner="Chat Tab" navigation={navigation} />
|
||||
);
|
||||
|
||||
MyChatScreen.navigationOptions = {
|
||||
tabBarLabel: 'Chat',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-chatboxes' : 'ios-chatboxes-outline'}
|
||||
size={26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
class MyChatScreen extends React.Component {
|
||||
static navigationOptions = {
|
||||
tabBarLabel: 'Chat',
|
||||
tabBarIcon: ({ tintColor, focused }) => (
|
||||
<Ionicons
|
||||
name={focused ? 'ios-chatboxes' : 'ios-chatboxes-outline'}
|
||||
size={26}
|
||||
style={{ color: tintColor }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
componentDidMount() {
|
||||
this._s0 = this.props.navigation.addListener('willFocus', this._onEvent);
|
||||
this._s1 = this.props.navigation.addListener('didFocus', this._onEvent);
|
||||
this._s2 = this.props.navigation.addListener('willBlur', this._onEvent);
|
||||
this._s3 = this.props.navigation.addListener('didBlur', this._onEvent);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this._s0.remove();
|
||||
this._s1.remove();
|
||||
this._s2.remove();
|
||||
this._s3.remove();
|
||||
}
|
||||
_onEvent = a => {
|
||||
console.log('EVENT ON CHAT TAB', a.type, a);
|
||||
};
|
||||
render() {
|
||||
const { navigation } = this.props;
|
||||
return <MyNavScreen banner="Chat Tab" navigation={navigation} />;
|
||||
}
|
||||
}
|
||||
|
||||
const MySettingsScreen = ({ navigation }) => (
|
||||
<MyNavScreen banner="Settings Tab" navigation={navigation} />
|
||||
@@ -128,10 +162,10 @@ class SimpleTabsContainer extends React.Component<SimpleTabsContainerProps> {
|
||||
_s3: EventListener;
|
||||
|
||||
componentDidMount() {
|
||||
this._s0 = this.props.navigation.addListener('willFocus', this._onWF);
|
||||
this._s1 = this.props.navigation.addListener('didFocus', this._onDF);
|
||||
this._s2 = this.props.navigation.addListener('willBlur', this._onWB);
|
||||
this._s3 = this.props.navigation.addListener('didBlur', this._onDB);
|
||||
this._s0 = this.props.navigation.addListener('willFocus', this._onAction);
|
||||
this._s1 = this.props.navigation.addListener('didFocus', this._onAction);
|
||||
this._s2 = this.props.navigation.addListener('willBlur', this._onAction);
|
||||
this._s3 = this.props.navigation.addListener('didBlur', this._onAction);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this._s0.remove();
|
||||
@@ -139,17 +173,8 @@ class SimpleTabsContainer extends React.Component<SimpleTabsContainerProps> {
|
||||
this._s2.remove();
|
||||
this._s3.remove();
|
||||
}
|
||||
_onWF = a => {
|
||||
console.log('_onWillFocus tabsExample ', a);
|
||||
};
|
||||
_onDF = a => {
|
||||
console.log('_onDidFocus tabsExample ', a);
|
||||
};
|
||||
_onWB = a => {
|
||||
console.log('_onWillBlur tabsExample ', a);
|
||||
};
|
||||
_onDB = a => {
|
||||
console.log('_onDidBlur tabsExample ', a);
|
||||
_onAction = a => {
|
||||
console.log('TABS EVENT', a.type, a);
|
||||
};
|
||||
render() {
|
||||
return <SimpleTabs navigation={this.props.navigation} />;
|
||||
|
||||
@@ -12,13 +12,10 @@
|
||||
"icon": "./assets/icons/react-navigation.png",
|
||||
"hideExponentText": false
|
||||
},
|
||||
"sdkVersion": "23.0.0",
|
||||
"sdkVersion": "24.0.0",
|
||||
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
|
||||
"packagerOpts": {
|
||||
"assetExts": [
|
||||
"ttf",
|
||||
"mp4"
|
||||
]
|
||||
"assetExts": ["ttf", "mp4"]
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"expo": "^23.0.0",
|
||||
"expo": "^24.0.2",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "16.0.0",
|
||||
"react-native": "^0.50.3",
|
||||
"react-native": "^0.51.0",
|
||||
"react-redux": "^5.0.6",
|
||||
"redux": "^3.7.2",
|
||||
"react-navigation": "link:../.."
|
||||
|
||||
@@ -13,17 +13,51 @@ export const AppNavigator = StackNavigator({
|
||||
Profile: { screen: ProfileScreen },
|
||||
});
|
||||
|
||||
const AppWithNavigationState = ({ dispatch, nav }) => (
|
||||
<AppNavigator navigation={addNavigationHelpers({ dispatch, state: nav })} />
|
||||
);
|
||||
class AppWithNavigationState extends React.Component {
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
nav: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
AppWithNavigationState.propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
nav: PropTypes.object.isRequired,
|
||||
};
|
||||
_actionEventSubscribers = new Set();
|
||||
|
||||
_addListener = (eventName, handler) => {
|
||||
eventName === 'action' && this._actionEventSubscribers.add(handler);
|
||||
return {
|
||||
remove: () => {
|
||||
this._actionEventSubscribers.delete(handler);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
componentDidUpdate(lastProps) {
|
||||
const lastState = lastProps.nav;
|
||||
this._actionEventSubscribers.forEach(subscriber => {
|
||||
subscriber({
|
||||
lastState: lastProps.nav,
|
||||
state: this.props.nav,
|
||||
action: this.props.lastAction,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dispatch, nav } = this.props;
|
||||
return (
|
||||
<AppNavigator
|
||||
navigation={addNavigationHelpers({
|
||||
dispatch,
|
||||
state: nav,
|
||||
addListener: this._addListener,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
nav: state.nav,
|
||||
lastAction: state.lastAction,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(AppWithNavigationState);
|
||||
|
||||
@@ -36,6 +36,10 @@ function nav(state = initialNavState, action) {
|
||||
return nextState || state;
|
||||
}
|
||||
|
||||
function lastAction(state = null, action) {
|
||||
return action;
|
||||
}
|
||||
|
||||
const initialAuthState = { isLoggedIn: false };
|
||||
|
||||
function auth(state = initialAuthState, action) {
|
||||
@@ -50,6 +54,7 @@ function auth(state = initialAuthState, action) {
|
||||
}
|
||||
|
||||
const AppReducer = combineReducers({
|
||||
lastAction,
|
||||
nav,
|
||||
auth,
|
||||
});
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
dependencies:
|
||||
cross-spawn "^5.1.0"
|
||||
|
||||
"@expo/vector-icons@^6.2.0":
|
||||
"@expo/vector-icons@^6.2.2":
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@expo/vector-icons/-/vector-icons-6.2.2.tgz#441edb58a52c0f4e5b4aba1e6f8da1e87cea7e11"
|
||||
dependencies:
|
||||
@@ -801,7 +801,7 @@ babel-plugin-transform-es3-property-literals@^6.8.0:
|
||||
dependencies:
|
||||
babel-runtime "^6.22.0"
|
||||
|
||||
babel-plugin-transform-exponentiation-operator@^6.24.1:
|
||||
babel-plugin-transform-exponentiation-operator@^6.24.1, babel-plugin-transform-exponentiation-operator@^6.5.0:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
|
||||
dependencies:
|
||||
@@ -1994,11 +1994,11 @@ expect@^21.2.1:
|
||||
jest-message-util "^21.2.1"
|
||||
jest-regex-util "^21.2.0"
|
||||
|
||||
expo@^23.0.0:
|
||||
version "23.0.6"
|
||||
resolved "https://registry.yarnpkg.com/expo/-/expo-23.0.6.tgz#8cb2c3992b385eb5866cc91f25f159420258d19a"
|
||||
expo@^24.0.2:
|
||||
version "24.0.2"
|
||||
resolved "https://registry.yarnpkg.com/expo/-/expo-24.0.2.tgz#3ff9784afd9efbb8eb739289aa53290ddf31a5a5"
|
||||
dependencies:
|
||||
"@expo/vector-icons" "^6.2.0"
|
||||
"@expo/vector-icons" "^6.2.2"
|
||||
babel-preset-expo "^4.0.0"
|
||||
fbemitter "^2.1.1"
|
||||
invariant "^2.2.2"
|
||||
@@ -3768,7 +3768,7 @@ methods@^1.1.1, methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
|
||||
metro-bundler@^0.20.1:
|
||||
metro-bundler@^0.20.0:
|
||||
version "0.20.3"
|
||||
resolved "https://registry.yarnpkg.com/metro-bundler/-/metro-bundler-0.20.3.tgz#0ded01b64e8963117017b106f75b83cfc34f3656"
|
||||
dependencies:
|
||||
@@ -4717,9 +4717,9 @@ react-native-vector-icons@4.4.2:
|
||||
prop-types "^15.5.10"
|
||||
yargs "^8.0.2"
|
||||
|
||||
react-native@^0.50.3:
|
||||
version "0.50.4"
|
||||
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.50.4.tgz#194f5da4939087b3acee712a503475f4942dca7e"
|
||||
react-native@^0.51.0:
|
||||
version "0.51.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.51.0.tgz#fe25934b3030fd323f3ca1a70f034133465955ed"
|
||||
dependencies:
|
||||
absolute-path "^0.0.0"
|
||||
art "^0.10.0"
|
||||
@@ -4727,6 +4727,7 @@ react-native@^0.50.3:
|
||||
babel-plugin-syntax-trailing-function-commas "^6.20.0"
|
||||
babel-plugin-transform-async-to-generator "6.16.0"
|
||||
babel-plugin-transform-class-properties "^6.18.0"
|
||||
babel-plugin-transform-exponentiation-operator "^6.5.0"
|
||||
babel-plugin-transform-flow-strip-types "^6.21.0"
|
||||
babel-plugin-transform-object-rest-spread "^6.20.2"
|
||||
babel-register "^6.24.1"
|
||||
@@ -4747,7 +4748,7 @@ react-native@^0.50.3:
|
||||
graceful-fs "^4.1.3"
|
||||
inquirer "^3.0.6"
|
||||
lodash "^4.16.6"
|
||||
metro-bundler "^0.20.1"
|
||||
metro-bundler "^0.20.0"
|
||||
mime "^1.3.4"
|
||||
minimist "^1.2.0"
|
||||
mkdirp "^0.5.1"
|
||||
@@ -4763,7 +4764,7 @@ react-native@^0.50.3:
|
||||
react-clone-referenced-element "^1.0.1"
|
||||
react-devtools-core "^2.5.0"
|
||||
react-timer-mixin "^0.13.2"
|
||||
regenerator-runtime "^0.9.5"
|
||||
regenerator-runtime "^0.11.0"
|
||||
rimraf "^2.5.4"
|
||||
semver "^5.0.3"
|
||||
shell-quote "1.6.1"
|
||||
@@ -4775,15 +4776,8 @@ react-native@^0.50.3:
|
||||
yargs "^9.0.0"
|
||||
|
||||
"react-navigation@link:../..":
|
||||
version "1.0.0-beta.27"
|
||||
dependencies:
|
||||
babel-plugin-transform-define "^1.3.0"
|
||||
clamp "^1.0.1"
|
||||
hoist-non-react-statics "^2.2.0"
|
||||
path-to-regexp "^1.7.0"
|
||||
prop-types "^15.5.10"
|
||||
react-native-drawer-layout-polyfill "^1.3.2"
|
||||
react-native-tab-view "^0.0.74"
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
react-proxy@^1.1.7:
|
||||
version "1.1.8"
|
||||
@@ -4924,10 +4918,6 @@ regenerator-runtime@^0.11.0:
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||
|
||||
regenerator-runtime@^0.9.5:
|
||||
version "0.9.6"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz#d33eb95d0d2001a4be39659707c51b0cb71ce029"
|
||||
|
||||
regenerator-transform@^0.10.0:
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"
|
||||
|
||||
437
src/__tests__/getChildEventSubscriber-test.js
Normal file
437
src/__tests__/getChildEventSubscriber-test.js
Normal file
@@ -0,0 +1,437 @@
|
||||
import getChildEventSubscriber from '../getChildEventSubscriber';
|
||||
|
||||
test('child action events only flow when focused', () => {
|
||||
const parentSubscriber = jest.fn();
|
||||
const emitParentAction = payload => {
|
||||
parentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const childEventSubscriber = getChildEventSubscriber(
|
||||
parentSubscriber,
|
||||
'key1'
|
||||
);
|
||||
const testState = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const focusedTestState = {
|
||||
...testState,
|
||||
index: 1,
|
||||
};
|
||||
const childActionHandler = jest.fn();
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
childEventSubscriber('action', childActionHandler);
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
state: focusedTestState,
|
||||
lastState: testState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
state: focusedTestState,
|
||||
lastState: focusedTestState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
test('grandchildren subscription', () => {
|
||||
const grandParentSubscriber = jest.fn();
|
||||
const emitGrandParentAction = payload => {
|
||||
grandParentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const parentSubscriber = getChildEventSubscriber(
|
||||
grandParentSubscriber,
|
||||
'parent'
|
||||
);
|
||||
const childEventSubscriber = getChildEventSubscriber(
|
||||
parentSubscriber,
|
||||
'key1'
|
||||
);
|
||||
const parentBlurState = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [
|
||||
{ key: 'aunt' },
|
||||
{
|
||||
key: 'parent',
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const parentTransitionState = {
|
||||
...parentBlurState,
|
||||
index: 1,
|
||||
isTransitioning: true,
|
||||
};
|
||||
const parentFocusState = {
|
||||
...parentTransitionState,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const childActionHandler = jest.fn();
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
childEventSubscriber('action', childActionHandler);
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: parentTransitionState,
|
||||
lastState: parentBlurState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(0);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: parentFocusState,
|
||||
lastState: parentTransitionState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
test('grandchildren transitions', () => {
|
||||
const grandParentSubscriber = jest.fn();
|
||||
const emitGrandParentAction = payload => {
|
||||
grandParentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const parentSubscriber = getChildEventSubscriber(
|
||||
grandParentSubscriber,
|
||||
'parent'
|
||||
);
|
||||
const childEventSubscriber = getChildEventSubscriber(
|
||||
parentSubscriber,
|
||||
'key1'
|
||||
);
|
||||
const makeFakeState = (childIndex, childIsTransitioning) => ({
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
routes: [
|
||||
{ key: 'nothing' },
|
||||
{
|
||||
key: 'parent',
|
||||
index: childIndex,
|
||||
isTransitioning: childIsTransitioning,
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }, { key: 'key2' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
const blurredState = makeFakeState(0, false);
|
||||
const transitionState = makeFakeState(1, true);
|
||||
const focusState = makeFakeState(1, false);
|
||||
const transition2State = makeFakeState(2, true);
|
||||
const blurred2State = makeFakeState(2, false);
|
||||
|
||||
const childActionHandler = jest.fn();
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('action', childActionHandler);
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: transitionState,
|
||||
lastState: blurredState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(0);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: focusState,
|
||||
lastState: transitionState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: focusState,
|
||||
lastState: focusState,
|
||||
action: { type: 'TestAction' },
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: transition2State,
|
||||
lastState: focusState,
|
||||
action: { type: 'CauseWillBlurAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(0);
|
||||
expect(childActionHandler.mock.calls.length).toBe(2);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: blurred2State,
|
||||
lastState: transition2State,
|
||||
action: { type: 'CauseDidBlurAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childActionHandler.mock.calls.length).toBe(3);
|
||||
});
|
||||
|
||||
test('pass through focus', () => {
|
||||
const parentSubscriber = jest.fn();
|
||||
const emitParentAction = payload => {
|
||||
parentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const childEventSubscriber = getChildEventSubscriber(
|
||||
parentSubscriber,
|
||||
'testKey'
|
||||
);
|
||||
const testRoute = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [{ key: 'key0' }, { key: 'testKey' }],
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitParentAction({
|
||||
type: 'willFocus',
|
||||
state: testRoute,
|
||||
lastState: testRoute,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'didFocus',
|
||||
state: testRoute,
|
||||
lastState: testRoute,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'willBlur',
|
||||
state: testRoute,
|
||||
lastState: testRoute,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'didBlur',
|
||||
state: testRoute,
|
||||
lastState: testRoute,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
test('child focus with transition', () => {
|
||||
const parentSubscriber = jest.fn();
|
||||
const emitParentAction = payload => {
|
||||
parentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const childEventSubscriber = getChildEventSubscriber(
|
||||
parentSubscriber,
|
||||
'key1'
|
||||
);
|
||||
const randomAction = { type: 'FooAction' };
|
||||
const testState = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitParentAction({
|
||||
type: 'didFocus',
|
||||
action: randomAction,
|
||||
lastState: testState,
|
||||
state: testState,
|
||||
});
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: testState,
|
||||
state: {
|
||||
...testState,
|
||||
index: 1,
|
||||
isTransitioning: true,
|
||||
},
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: {
|
||||
...testState,
|
||||
index: 1,
|
||||
isTransitioning: true,
|
||||
},
|
||||
state: {
|
||||
...testState,
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
},
|
||||
});
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: {
|
||||
...testState,
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
},
|
||||
state: {
|
||||
...testState,
|
||||
index: 0,
|
||||
isTransitioning: true,
|
||||
},
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: {
|
||||
...testState,
|
||||
index: 0,
|
||||
isTransitioning: true,
|
||||
},
|
||||
state: {
|
||||
...testState,
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
},
|
||||
});
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
test('child focus with immediate transition', () => {
|
||||
const parentSubscriber = jest.fn();
|
||||
const emitParentAction = payload => {
|
||||
parentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const childEventSubscriber = getChildEventSubscriber(
|
||||
parentSubscriber,
|
||||
'key1'
|
||||
);
|
||||
const randomAction = { type: 'FooAction' };
|
||||
const testState = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitParentAction({
|
||||
type: 'didFocus',
|
||||
action: randomAction,
|
||||
lastState: testState,
|
||||
state: testState,
|
||||
});
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: testState,
|
||||
state: {
|
||||
...testState,
|
||||
index: 1,
|
||||
},
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: {
|
||||
...testState,
|
||||
index: 1,
|
||||
},
|
||||
state: {
|
||||
...testState,
|
||||
index: 0,
|
||||
},
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
@@ -32,13 +32,12 @@ export default function getChildEventSubscriber(addListener, key) {
|
||||
const subscribers = getChildSubscribers(payload.type);
|
||||
subscribers &&
|
||||
subscribers.forEach(subs => {
|
||||
// $FlowFixMe - Payload should probably understand generic state type
|
||||
subs(payload);
|
||||
});
|
||||
};
|
||||
|
||||
let isSelfFocused = false;
|
||||
|
||||
let isParentFocused = true;
|
||||
let isChildFocused = false;
|
||||
const cleanup = () => {
|
||||
upstreamSubscribers.forEach(subs => subs && subs.remove());
|
||||
};
|
||||
@@ -54,74 +53,112 @@ export default function getChildEventSubscriber(addListener, key) {
|
||||
const upstreamSubscribers = upstreamEvents.map(eventName =>
|
||||
addListener(eventName, payload => {
|
||||
const { state, lastState, action } = payload;
|
||||
const lastFocusKey = lastState && lastState.routes[lastState.index].key;
|
||||
const focusKey = state && state.routes[state.index].key;
|
||||
const lastRoutes = lastState && lastState.routes;
|
||||
const routes = state && state.routes;
|
||||
const lastFocusKey =
|
||||
lastState && lastState.routes && lastState.routes[lastState.index].key;
|
||||
const focusKey = routes && routes[state.index].key;
|
||||
|
||||
const isFocused = focusKey === key;
|
||||
const wasFocused = lastFocusKey === key;
|
||||
const lastRoute =
|
||||
lastState && lastState.routes.find(route => route.key === key);
|
||||
const newRoute = state && state.routes.find(route => route.key === key);
|
||||
lastRoutes && lastRoutes.find(route => route.key === key);
|
||||
const newRoute = routes && routes.find(route => route.key === key);
|
||||
const eventContext = payload.context || 'Root';
|
||||
const childPayload = {
|
||||
context: `${key}:${action.type}_${eventContext}`,
|
||||
state: newRoute,
|
||||
lastState: lastRoute,
|
||||
action,
|
||||
type: eventName,
|
||||
};
|
||||
|
||||
const didNavigate =
|
||||
(lastState && lastState.isTransitioning) !==
|
||||
(state && state.isTransitioning);
|
||||
|
||||
const isTransitioning = !!state && state.isTransitioning;
|
||||
const wasTransitioning = !!lastState && lastState.isTransitioning;
|
||||
const didStartTransitioning = !wasTransitioning && isTransitioning;
|
||||
const didFinishTransitioning = wasTransitioning && !isTransitioning;
|
||||
|
||||
const wasChildFocused = isChildFocused;
|
||||
if (eventName !== 'action') {
|
||||
switch (eventName) {
|
||||
case 'didFocus':
|
||||
isSelfFocused = true;
|
||||
isParentFocused = true;
|
||||
break;
|
||||
case 'didBlur':
|
||||
isSelfFocused = false;
|
||||
isParentFocused = false;
|
||||
break;
|
||||
}
|
||||
emit(childPayload);
|
||||
if (isFocused && eventName === 'willFocus') {
|
||||
emit(childPayload);
|
||||
}
|
||||
if (isFocused && !isTransitioning && eventName === 'didFocus') {
|
||||
emit(childPayload);
|
||||
isChildFocused = true;
|
||||
}
|
||||
if (isFocused && eventName === 'willBlur') {
|
||||
emit(childPayload);
|
||||
}
|
||||
if (isFocused && !isTransitioning && eventName === 'didBlur') {
|
||||
emit(childPayload);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// now we're exclusively handling the "action" event
|
||||
if (!isParentFocused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newRoute) {
|
||||
// fire this event to pass navigation events to children subscribers
|
||||
if (isChildFocused && newRoute) {
|
||||
// fire this action event to pass navigation events to children subscribers
|
||||
emit(childPayload);
|
||||
}
|
||||
if (isFocused && didStartTransitioning && !isSelfFocused) {
|
||||
if (isFocused && didStartTransitioning && !isChildFocused) {
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'willFocus',
|
||||
});
|
||||
}
|
||||
if (isFocused && didFinishTransitioning && !isSelfFocused) {
|
||||
if (isFocused && didFinishTransitioning && !isChildFocused) {
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'didFocus',
|
||||
});
|
||||
isSelfFocused = true;
|
||||
isChildFocused = true;
|
||||
}
|
||||
if (!isFocused && didStartTransitioning && isSelfFocused) {
|
||||
if (isFocused && !isChildFocused && !didStartTransitioning) {
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'willFocus',
|
||||
});
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'didFocus',
|
||||
});
|
||||
isChildFocused = true;
|
||||
}
|
||||
if (!isFocused && didStartTransitioning && isChildFocused) {
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'willBlur',
|
||||
});
|
||||
}
|
||||
if (!isFocused && didFinishTransitioning && isSelfFocused) {
|
||||
if (!isFocused && didFinishTransitioning && isChildFocused) {
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'didBlur',
|
||||
});
|
||||
isSelfFocused = false;
|
||||
isChildFocused = false;
|
||||
}
|
||||
if (!isFocused && isChildFocused && !didStartTransitioning) {
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'willBlur',
|
||||
});
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'didBlur',
|
||||
});
|
||||
isChildFocused = false;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user