Extract core codebase to @react-navigation/core
@ -505,6 +505,16 @@
|
|||||||
pouchdb-collections "^1.0.1"
|
pouchdb-collections "^1.0.1"
|
||||||
tiny-queue "^0.2.1"
|
tiny-queue "^0.2.1"
|
||||||
|
|
||||||
|
"@react-navigation/core@^3.0.0-alpha.2":
|
||||||
|
version "3.0.0-alpha.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-3.0.0-alpha.2.tgz#a47de58078ae1178cf37aeaac2512d78a0907451"
|
||||||
|
integrity sha512-8tkJhxFJCrAujPZNPsfnkj16E2L8KTZNH89Z9hF+zNIebYGfWphLBzx/xbdNk8nC9SFstBppj1k/KJE6ilaSmA==
|
||||||
|
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"
|
||||||
|
|
||||||
abab@^2.0.0:
|
abab@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
|
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
|
||||||
@ -1802,11 +1812,6 @@ ci-info@^1.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
|
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
|
||||||
integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
|
integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
|
||||||
|
|
||||||
clamp@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/clamp/-/clamp-1.0.1.tgz#66a0e64011816e37196828fdc8c8c147312c8634"
|
|
||||||
integrity sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ=
|
|
||||||
|
|
||||||
class-utils@^0.3.5:
|
class-utils@^0.3.5:
|
||||||
version "0.3.6"
|
version "0.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
|
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
|
||||||
@ -2036,15 +2041,7 @@ create-react-class@^15.6.3:
|
|||||||
loose-envify "^1.3.1"
|
loose-envify "^1.3.1"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
create-react-context@0.2.2:
|
create-react-context@^0.2.2, create-react-context@^0.2.3:
|
||||||
version "0.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.2.tgz#9836542f9aaa22868cd7d4a6f82667df38019dca"
|
|
||||||
integrity sha512-KkpaLARMhsTsgp0d2NA/R94F/eDLbhXERdIq3LvX2biCAXcDvHYoOqHfWCHf1+OLj+HKBotLG3KqaOOf+C1C+A==
|
|
||||||
dependencies:
|
|
||||||
fbjs "^0.8.0"
|
|
||||||
gud "^1.0.0"
|
|
||||||
|
|
||||||
create-react-context@^0.2.2:
|
|
||||||
version "0.2.3"
|
version "0.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.3.tgz#9ec140a6914a22ef04b8b09b7771de89567cb6f3"
|
resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.3.tgz#9ec140a6914a22ef04b8b09b7771de89567cb6f3"
|
||||||
integrity sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==
|
integrity sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==
|
||||||
@ -3233,11 +3230,18 @@ has@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.1"
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
hoist-non-react-statics@^2.2.0, hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
|
hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
|
||||||
version "2.5.5"
|
version "2.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
||||||
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
|
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
|
||||||
|
|
||||||
|
hoist-non-react-statics@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.0.1.tgz#fba3e7df0210eb9447757ca1a7cb607162f0a364"
|
||||||
|
integrity sha512-1kXwPsOi0OGQIZNVMPvgWJ9tSnGMiMfJdihqEzrPEXlHOBh9AAHXX/QYmAJTXztnz/K+PQ8ryCb4eGaN6HlGbQ==
|
||||||
|
dependencies:
|
||||||
|
react-is "^16.3.2"
|
||||||
|
|
||||||
home-or-tmp@^2.0.0:
|
home-or-tmp@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
|
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
|
||||||
@ -3589,11 +3593,6 @@ is-windows@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||||
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
|
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
|
||||||
|
|
||||||
isarray@0.0.1:
|
|
||||||
version "0.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
|
||||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
|
||||||
|
|
||||||
isarray@1.0.0, isarray@~1.0.0:
|
isarray@1.0.0, isarray@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||||
@ -5075,12 +5074,10 @@ path-parse@^1.0.5:
|
|||||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
||||||
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
||||||
|
|
||||||
path-to-regexp@^1.7.0:
|
path-to-regexp@^2.4.0:
|
||||||
version "1.7.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704"
|
||||||
integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=
|
integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==
|
||||||
dependencies:
|
|
||||||
isarray "0.0.1"
|
|
||||||
|
|
||||||
path-type@^1.0.0:
|
path-type@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
@ -5275,7 +5272,7 @@ qs@^6.5.0, qs@~6.5.2:
|
|||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||||
|
|
||||||
query-string@^6.1.0:
|
query-string@^6.2.0:
|
||||||
version "6.2.0"
|
version "6.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.2.0.tgz#468edeb542b7e0538f9f9b1aeb26f034f19c86e1"
|
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.2.0.tgz#468edeb542b7e0538f9f9b1aeb26f034f19c86e1"
|
||||||
integrity sha512-5wupExkIt8RYL4h/FE+WTg3JHk62e6fFPWtAZA9J5IWK1PfTfKkMS93HBUHcFpeYi9KsY5pFbh+ldvEyaz5MyA==
|
integrity sha512-5wupExkIt8RYL4h/FE+WTg3JHk62e6fFPWtAZA9J5IWK1PfTfKkMS93HBUHcFpeYi9KsY5pFbh+ldvEyaz5MyA==
|
||||||
@ -5325,7 +5322,7 @@ react-devtools-core@3.1.0:
|
|||||||
shell-quote "^1.6.1"
|
shell-quote "^1.6.1"
|
||||||
ws "^2.0.3"
|
ws "^2.0.3"
|
||||||
|
|
||||||
react-is@^16.3.1, react-is@^16.5.2:
|
react-is@^16.3.1, react-is@^16.3.2, react-is@^16.5.2:
|
||||||
version "16.5.2"
|
version "16.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
|
||||||
integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==
|
integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==
|
||||||
@ -5349,6 +5346,15 @@ react-native-gesture-handler@1.0.6:
|
|||||||
invariant "^2.2.2"
|
invariant "^2.2.2"
|
||||||
prop-types "^15.5.10"
|
prop-types "^15.5.10"
|
||||||
|
|
||||||
|
react-native-gesture-handler@^1.0.0:
|
||||||
|
version "1.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-1.0.8.tgz#c2799741bf6443bb542892b0a36201a6d9ded209"
|
||||||
|
integrity sha512-Lc6PV5nKXgZdDeky96yi6gAM1UJHaYwzZbZyph0YuSv/L6vTtN+KPGsKyIENoOyxLJ/i43MSNn7fR+Xbv0w/xA==
|
||||||
|
dependencies:
|
||||||
|
hoist-non-react-statics "^2.3.1"
|
||||||
|
invariant "^2.2.2"
|
||||||
|
prop-types "^15.5.10"
|
||||||
|
|
||||||
react-native-iphone-x-helper@^1.0.2:
|
react-native-iphone-x-helper@^1.0.2:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.2.0.tgz#9f8a376eb00bc712115abff4420318a0063fa796"
|
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.2.0.tgz#9f8a376eb00bc712115abff4420318a0063fa796"
|
||||||
|
@ -30,11 +30,7 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clamp": "^1.0.1",
|
"@react-navigation/core": "^3.0.0-alpha.2",
|
||||||
"create-react-context": "0.2.2",
|
|
||||||
"hoist-non-react-statics": "^2.2.0",
|
|
||||||
"path-to-regexp": "^1.7.0",
|
|
||||||
"query-string": "^6.1.0",
|
|
||||||
"react-lifecycles-compat": "^3",
|
"react-lifecycles-compat": "^3",
|
||||||
"react-native-gesture-handler": "^1.0.0",
|
"react-native-gesture-handler": "^1.0.0",
|
||||||
"react-native-safe-area-view": "0.11.0",
|
"react-native-safe-area-view": "0.11.0",
|
||||||
@ -93,7 +89,7 @@
|
|||||||
"<rootDir>/examples/"
|
"<rootDir>/examples/"
|
||||||
],
|
],
|
||||||
"transformIgnorePatterns": [
|
"transformIgnorePatterns": [
|
||||||
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation-deprecated-tab-navigator|react-navigation-stack)"
|
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation-deprecated-tab-navigator|react-navigation-stack|@react-navigation/core)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
const BACK = 'Navigation/BACK';
|
|
||||||
const INIT = 'Navigation/INIT';
|
|
||||||
const NAVIGATE = 'Navigation/NAVIGATE';
|
|
||||||
const SET_PARAMS = 'Navigation/SET_PARAMS';
|
|
||||||
|
|
||||||
const back = (payload = {}) => ({
|
|
||||||
type: BACK,
|
|
||||||
key: payload.key,
|
|
||||||
immediate: payload.immediate,
|
|
||||||
});
|
|
||||||
|
|
||||||
const init = (payload = {}) => {
|
|
||||||
const action = {
|
|
||||||
type: INIT,
|
|
||||||
};
|
|
||||||
if (payload.params) {
|
|
||||||
action.params = payload.params;
|
|
||||||
}
|
|
||||||
return action;
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigate = payload => {
|
|
||||||
const action = {
|
|
||||||
type: NAVIGATE,
|
|
||||||
routeName: payload.routeName,
|
|
||||||
};
|
|
||||||
if (payload.params) {
|
|
||||||
action.params = payload.params;
|
|
||||||
}
|
|
||||||
if (payload.action) {
|
|
||||||
action.action = payload.action;
|
|
||||||
}
|
|
||||||
if (payload.key) {
|
|
||||||
action.key = payload.key;
|
|
||||||
}
|
|
||||||
return action;
|
|
||||||
};
|
|
||||||
|
|
||||||
const setParams = payload => ({
|
|
||||||
type: SET_PARAMS,
|
|
||||||
key: payload.key,
|
|
||||||
params: payload.params,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
// Action constants
|
|
||||||
BACK,
|
|
||||||
INIT,
|
|
||||||
NAVIGATE,
|
|
||||||
SET_PARAMS,
|
|
||||||
|
|
||||||
// Action creators
|
|
||||||
back,
|
|
||||||
init,
|
|
||||||
navigate,
|
|
||||||
setParams,
|
|
||||||
};
|
|
@ -1,204 +0,0 @@
|
|||||||
import invariant from './utils/invariant';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utilities to perform atomic operation with navigate state and routes.
|
|
||||||
*
|
|
||||||
* ```javascript
|
|
||||||
* const state1 = {key: 'screen 1'};
|
|
||||||
* const state2 = NavigationStateUtils.push(state1, {key: 'screen 2'});
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
const StateUtils = {
|
|
||||||
/**
|
|
||||||
* Gets a route by key. If the route isn't found, returns `null`.
|
|
||||||
*/
|
|
||||||
get(state, key) {
|
|
||||||
return state.routes.find(route => route.key === key) || null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the first index at which a given route's key can be found in the
|
|
||||||
* routes of the navigation state, or -1 if it is not present.
|
|
||||||
*/
|
|
||||||
indexOf(state, key) {
|
|
||||||
return state.routes.findIndex(route => route.key === key);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns `true` at which a given route's key can be found in the
|
|
||||||
* routes of the navigation state.
|
|
||||||
*/
|
|
||||||
has(state, key) {
|
|
||||||
return !!state.routes.some(route => route.key === key);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes a new route into the navigation state.
|
|
||||||
* Note that this moves the index to the positon to where the last route in the
|
|
||||||
* stack is at.
|
|
||||||
*/
|
|
||||||
push(state, route) {
|
|
||||||
invariant(
|
|
||||||
StateUtils.indexOf(state, route.key) === -1,
|
|
||||||
'should not push route with duplicated key %s',
|
|
||||||
route.key
|
|
||||||
);
|
|
||||||
|
|
||||||
const routes = state.routes.slice();
|
|
||||||
routes.push(route);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
index: routes.length - 1,
|
|
||||||
routes,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pops out a route from the navigation state.
|
|
||||||
* Note that this moves the index to the positon to where the last route in the
|
|
||||||
* stack is at.
|
|
||||||
*/
|
|
||||||
pop(state) {
|
|
||||||
if (state.index <= 0) {
|
|
||||||
// [Note]: Over-popping does not throw error. Instead, it will be no-op.
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
const routes = state.routes.slice(0, -1);
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
index: routes.length - 1,
|
|
||||||
routes,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the focused route of the navigation state by index.
|
|
||||||
*/
|
|
||||||
jumpToIndex(state, index) {
|
|
||||||
if (index === state.index) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
invariant(!!state.routes[index], 'invalid index %s to jump to', index);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
index,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the focused route of the navigation state by key.
|
|
||||||
*/
|
|
||||||
jumpTo(state, key) {
|
|
||||||
const index = StateUtils.indexOf(state, key);
|
|
||||||
return StateUtils.jumpToIndex(state, index);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the focused route to the previous route.
|
|
||||||
*/
|
|
||||||
back(state) {
|
|
||||||
const index = state.index - 1;
|
|
||||||
const route = state.routes[index];
|
|
||||||
return route ? StateUtils.jumpToIndex(state, index) : state;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the focused route to the next route.
|
|
||||||
*/
|
|
||||||
forward(state) {
|
|
||||||
const index = state.index + 1;
|
|
||||||
const route = state.routes[index];
|
|
||||||
return route ? StateUtils.jumpToIndex(state, index) : state;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace a route by a key.
|
|
||||||
* Note that this moves the index to the position to where the new route in the
|
|
||||||
* stack is at and updates the routes array accordingly.
|
|
||||||
*/
|
|
||||||
replaceAndPrune(state, key, route) {
|
|
||||||
const index = StateUtils.indexOf(state, key);
|
|
||||||
const replaced = StateUtils.replaceAtIndex(state, index, route);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...replaced,
|
|
||||||
routes: replaced.routes.slice(0, index + 1),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace a route by a key.
|
|
||||||
* Note that this moves the index to the position to where the new route in the
|
|
||||||
* stack is at. Does not prune the routes.
|
|
||||||
* If preserveIndex is true then replacing the route does not cause the index
|
|
||||||
* to change to the index of that route.
|
|
||||||
*/
|
|
||||||
replaceAt(state, key, route, preserveIndex = false) {
|
|
||||||
const index = StateUtils.indexOf(state, key);
|
|
||||||
const nextIndex = preserveIndex ? state.index : index;
|
|
||||||
let nextState = StateUtils.replaceAtIndex(state, index, route);
|
|
||||||
nextState.index = nextIndex;
|
|
||||||
return nextState;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace a route by a index.
|
|
||||||
* Note that this moves the index to the positon to where the new route in the
|
|
||||||
* stack is at.
|
|
||||||
*/
|
|
||||||
replaceAtIndex(state, index, route) {
|
|
||||||
invariant(
|
|
||||||
!!state.routes[index],
|
|
||||||
'invalid index %s for replacing route %s',
|
|
||||||
index,
|
|
||||||
route.key
|
|
||||||
);
|
|
||||||
|
|
||||||
if (state.routes[index] === route && index === state.index) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
const routes = state.routes.slice();
|
|
||||||
routes[index] = route;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
index,
|
|
||||||
routes,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets all routes.
|
|
||||||
* Note that this moves the index to the position to where the last route in the
|
|
||||||
* stack is at if the param `index` isn't provided.
|
|
||||||
*/
|
|
||||||
reset(state, routes, index) {
|
|
||||||
invariant(
|
|
||||||
routes.length && Array.isArray(routes),
|
|
||||||
'invalid routes to replace'
|
|
||||||
);
|
|
||||||
|
|
||||||
const nextIndex = index === undefined ? routes.length - 1 : index;
|
|
||||||
|
|
||||||
if (state.routes.length === routes.length && state.index === nextIndex) {
|
|
||||||
const compare = (route, ii) => routes[ii] === route;
|
|
||||||
if (state.routes.every(compare)) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
invariant(!!routes[nextIndex], 'invalid index %s to reset', nextIndex);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
index: nextIndex,
|
|
||||||
routes,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default StateUtils;
|
|
@ -1,57 +0,0 @@
|
|||||||
import NavigationActions from '../NavigationActions';
|
|
||||||
|
|
||||||
describe('generic navigation actions', () => {
|
|
||||||
const params = { foo: 'bar' };
|
|
||||||
const navigateAction = NavigationActions.navigate({ routeName: 'another' });
|
|
||||||
|
|
||||||
it('exports back action and type', () => {
|
|
||||||
expect(NavigationActions.back()).toEqual({ type: NavigationActions.BACK });
|
|
||||||
expect(NavigationActions.back({ key: 'test' })).toEqual({
|
|
||||||
type: NavigationActions.BACK,
|
|
||||||
key: 'test',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('exports init action and type', () => {
|
|
||||||
expect(NavigationActions.init()).toEqual({ type: NavigationActions.INIT });
|
|
||||||
expect(NavigationActions.init({ params })).toEqual({
|
|
||||||
type: NavigationActions.INIT,
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('exports navigate action and type', () => {
|
|
||||||
expect(NavigationActions.navigate({ routeName: 'test' })).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'test',
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
NavigationActions.navigate({
|
|
||||||
routeName: 'test',
|
|
||||||
params,
|
|
||||||
action: navigateAction,
|
|
||||||
})
|
|
||||||
).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'test',
|
|
||||||
params,
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'another',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('exports setParams action and type', () => {
|
|
||||||
expect(
|
|
||||||
NavigationActions.setParams({
|
|
||||||
key: 'test',
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
).toEqual({
|
|
||||||
type: NavigationActions.SET_PARAMS,
|
|
||||||
key: 'test',
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -3,15 +3,16 @@ import { View } from 'react-native';
|
|||||||
|
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
import NavigationActions from '../NavigationActions';
|
|
||||||
|
|
||||||
import createNavigationContainer, {
|
import createNavigationContainer, {
|
||||||
_TESTING_ONLY_reset_container_count,
|
_TESTING_ONLY_reset_container_count,
|
||||||
} from '../createNavigationContainer';
|
} from '../createNavigationContainer';
|
||||||
|
|
||||||
import createNavigator from '../navigators/createNavigator';
|
import {
|
||||||
import StackRouter from '../routers/StackRouter';
|
NavigationActions,
|
||||||
import SwitchView from '../views/SwitchView/SwitchView';
|
createNavigator,
|
||||||
|
StackRouter,
|
||||||
|
SwitchView,
|
||||||
|
} from '@react-navigation/core';
|
||||||
|
|
||||||
function createStackNavigator(routeConfigMap, stackConfig = {}) {
|
function createStackNavigator(routeConfigMap, stackConfig = {}) {
|
||||||
const router = StackRouter(routeConfigMap, stackConfig);
|
const router = StackRouter(routeConfigMap, stackConfig);
|
||||||
|
@ -1,267 +0,0 @@
|
|||||||
import NavigationStateUtils from '../StateUtils';
|
|
||||||
|
|
||||||
const routeName = 'Anything';
|
|
||||||
|
|
||||||
describe('StateUtils', () => {
|
|
||||||
// Getters
|
|
||||||
it('gets route', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(NavigationStateUtils.get(state, 'a')).toEqual({
|
|
||||||
key: 'a',
|
|
||||||
routeName,
|
|
||||||
});
|
|
||||||
expect(NavigationStateUtils.get(state, 'b')).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('gets route index', () => {
|
|
||||||
const state = {
|
|
||||||
index: 1,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(NavigationStateUtils.indexOf(state, 'a')).toBe(0);
|
|
||||||
expect(NavigationStateUtils.indexOf(state, 'b')).toBe(1);
|
|
||||||
expect(NavigationStateUtils.indexOf(state, 'c')).toBe(-1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has a route', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(NavigationStateUtils.has(state, 'b')).toBe(true);
|
|
||||||
expect(NavigationStateUtils.has(state, 'c')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Push
|
|
||||||
it('pushes a route', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
const newState = {
|
|
||||||
index: 1,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
};
|
|
||||||
expect(NavigationStateUtils.push(state, { key: 'b', routeName })).toEqual(
|
|
||||||
newState
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not push duplicated route', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(() =>
|
|
||||||
NavigationStateUtils.push(state, { key: 'a', routeName })
|
|
||||||
).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pop
|
|
||||||
it('pops route', () => {
|
|
||||||
const state = {
|
|
||||||
index: 1,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
const newState = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(NavigationStateUtils.pop(state)).toEqual(newState);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not pop route if not applicable', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(NavigationStateUtils.pop(state)).toBe(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Jump
|
|
||||||
it('jumps to new index', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
const newState = {
|
|
||||||
index: 1,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(NavigationStateUtils.jumpToIndex(state, 0)).toBe(state);
|
|
||||||
expect(NavigationStateUtils.jumpToIndex(state, 1)).toEqual(newState);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if jumps to invalid index', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(() => NavigationStateUtils.jumpToIndex(state, 2)).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('jumps to new key', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
const newState = {
|
|
||||||
index: 1,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(NavigationStateUtils.jumpTo(state, 'a')).toBe(state);
|
|
||||||
expect(NavigationStateUtils.jumpTo(state, 'b')).toEqual(newState);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if jumps to invalid key', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(() => NavigationStateUtils.jumpTo(state, 'c')).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('move backwards', () => {
|
|
||||||
const state = {
|
|
||||||
index: 1,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
const newState = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(NavigationStateUtils.back(state)).toEqual(newState);
|
|
||||||
expect(NavigationStateUtils.back(newState)).toBe(newState);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('move forwards', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
const newState = {
|
|
||||||
index: 1,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(NavigationStateUtils.forward(state)).toEqual(newState);
|
|
||||||
expect(NavigationStateUtils.forward(newState)).toBe(newState);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Replace
|
|
||||||
it('Replaces by key', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
const newState = {
|
|
||||||
index: 1,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(
|
|
||||||
NavigationStateUtils.replaceAt(state, 'b', { key: 'c', routeName })
|
|
||||||
).toEqual(newState);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Replaces by index', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
const newState = {
|
|
||||||
index: 1,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(
|
|
||||||
NavigationStateUtils.replaceAtIndex(state, 1, { key: 'c', routeName })
|
|
||||||
).toEqual(newState);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Returns the state with updated index if route is unchanged but index changes', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(
|
|
||||||
NavigationStateUtils.replaceAtIndex(state, 1, state.routes[1])
|
|
||||||
).toEqual({ ...state, index: 1 });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reset
|
|
||||||
it('Resets routes', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
const newState = {
|
|
||||||
index: 1,
|
|
||||||
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(
|
|
||||||
NavigationStateUtils.reset(state, [
|
|
||||||
{ key: 'x', routeName },
|
|
||||||
{ key: 'y', routeName },
|
|
||||||
])
|
|
||||||
).toEqual(newState);
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
NavigationStateUtils.reset(state, []);
|
|
||||||
}).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Resets routes with index', () => {
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
const newState = {
|
|
||||||
index: 0,
|
|
||||||
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(
|
|
||||||
NavigationStateUtils.reset(
|
|
||||||
state,
|
|
||||||
[{ key: 'x', routeName }, { key: 'y', routeName }],
|
|
||||||
0
|
|
||||||
)
|
|
||||||
).toEqual(newState);
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
NavigationStateUtils.reset(
|
|
||||||
state,
|
|
||||||
[{ key: 'x', routeName }, { key: 'y', routeName }],
|
|
||||||
100
|
|
||||||
);
|
|
||||||
}).toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,460 +0,0 @@
|
|||||||
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')
|
|
||||||
.addListener;
|
|
||||||
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'
|
|
||||||
).addListener;
|
|
||||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
|
||||||
.addListener;
|
|
||||||
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'
|
|
||||||
).addListener;
|
|
||||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
|
||||||
.addListener;
|
|
||||||
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(1);
|
|
||||||
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(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('grandchildren pass through 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'
|
|
||||||
).addListener;
|
|
||||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
|
||||||
.addListener;
|
|
||||||
const makeFakeState = (childIndex, childIsTransitioning) => ({
|
|
||||||
index: childIndex,
|
|
||||||
isTransitioning: childIsTransitioning,
|
|
||||||
routes: [
|
|
||||||
{ key: 'nothing' },
|
|
||||||
{
|
|
||||||
key: 'parent',
|
|
||||||
index: 1,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [{ key: 'key0' }, { key: 'key1' }, { key: 'key2' }],
|
|
||||||
},
|
|
||||||
].slice(0, childIndex + 1),
|
|
||||||
});
|
|
||||||
const blurredState = makeFakeState(0, false);
|
|
||||||
const transitionState = makeFakeState(1, true);
|
|
||||||
const focusState = makeFakeState(1, false);
|
|
||||||
const transition2State = makeFakeState(0, true);
|
|
||||||
const blurred2State = makeFakeState(0, 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(1);
|
|
||||||
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(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')
|
|
||||||
.addListener;
|
|
||||||
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')
|
|
||||||
.addListener;
|
|
||||||
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);
|
|
||||||
});
|
|
@ -1,102 +0,0 @@
|
|||||||
import getNavigation from '../getNavigation';
|
|
||||||
|
|
||||||
test('getNavigation provides default action helpers', () => {
|
|
||||||
const router = {
|
|
||||||
getActionCreators: () => ({}),
|
|
||||||
getStateForAction(action, lastState = {}) {
|
|
||||||
return lastState;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const dispatch = jest.fn();
|
|
||||||
|
|
||||||
const topNav = getNavigation(
|
|
||||||
router,
|
|
||||||
{},
|
|
||||||
dispatch,
|
|
||||||
new Set(),
|
|
||||||
() => ({}),
|
|
||||||
() => topNav
|
|
||||||
);
|
|
||||||
|
|
||||||
topNav.navigate('GreatRoute');
|
|
||||||
|
|
||||||
expect(dispatch.mock.calls.length).toBe(1);
|
|
||||||
expect(dispatch.mock.calls[0][0].type).toBe('Navigation/NAVIGATE');
|
|
||||||
expect(dispatch.mock.calls[0][0].routeName).toBe('GreatRoute');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getNavigation provides router action helpers', () => {
|
|
||||||
const router = {
|
|
||||||
getActionCreators: () => ({
|
|
||||||
foo: bar => ({ type: 'FooBarAction', bar }),
|
|
||||||
}),
|
|
||||||
getStateForAction(action, lastState = {}) {
|
|
||||||
return lastState;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const dispatch = jest.fn();
|
|
||||||
|
|
||||||
const topNav = getNavigation(
|
|
||||||
router,
|
|
||||||
{},
|
|
||||||
dispatch,
|
|
||||||
new Set(),
|
|
||||||
() => ({}),
|
|
||||||
() => topNav
|
|
||||||
);
|
|
||||||
|
|
||||||
topNav.foo('Great');
|
|
||||||
|
|
||||||
expect(dispatch.mock.calls.length).toBe(1);
|
|
||||||
expect(dispatch.mock.calls[0][0].type).toBe('FooBarAction');
|
|
||||||
expect(dispatch.mock.calls[0][0].bar).toBe('Great');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getNavigation get child navigation with router', () => {
|
|
||||||
const actionSubscribers = new Set();
|
|
||||||
let navigation = null;
|
|
||||||
|
|
||||||
const routerA = {
|
|
||||||
getActionCreators: () => ({}),
|
|
||||||
getStateForAction(action, lastState = {}) {
|
|
||||||
return lastState;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const router = {
|
|
||||||
childRouters: {
|
|
||||||
RouteA: routerA,
|
|
||||||
},
|
|
||||||
getActionCreators: () => ({}),
|
|
||||||
getStateForAction(action, lastState = {}) {
|
|
||||||
return lastState;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const initState = {
|
|
||||||
index: 0,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
key: 'a',
|
|
||||||
routeName: 'RouteA',
|
|
||||||
routes: [{ key: 'c', routeName: 'RouteC' }],
|
|
||||||
index: 0,
|
|
||||||
},
|
|
||||||
{ key: 'b', routeName: 'RouteB' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const topNav = getNavigation(
|
|
||||||
router,
|
|
||||||
initState,
|
|
||||||
() => {},
|
|
||||||
actionSubscribers,
|
|
||||||
() => ({}),
|
|
||||||
() => navigation
|
|
||||||
);
|
|
||||||
|
|
||||||
const childNavA = topNav.getChildNavigation('a');
|
|
||||||
|
|
||||||
expect(childNavA.router).toBe(routerA);
|
|
||||||
});
|
|
@ -2,11 +2,15 @@ import React from 'react';
|
|||||||
import { AsyncStorage, Linking, Platform, BackHandler } from 'react-native';
|
import { AsyncStorage, Linking, Platform, BackHandler } from 'react-native';
|
||||||
import { polyfill } from 'react-lifecycles-compat';
|
import { polyfill } from 'react-lifecycles-compat';
|
||||||
|
|
||||||
import NavigationActions from './NavigationActions';
|
import {
|
||||||
import getNavigation from './getNavigation';
|
NavigationActions,
|
||||||
|
pathUtils,
|
||||||
|
getNavigation,
|
||||||
|
} from '@react-navigation/core';
|
||||||
import invariant from './utils/invariant';
|
import invariant from './utils/invariant';
|
||||||
import docsUrl from './utils/docsUrl';
|
import docsUrl from './utils/docsUrl';
|
||||||
import { urlToPathAndParams } from './routers/pathUtils';
|
|
||||||
|
const { urlToPathAndParams } = pathUtils;
|
||||||
|
|
||||||
function isStateful(props) {
|
function isStateful(props) {
|
||||||
return !props.navigation;
|
return !props.navigation;
|
||||||
|
@ -1,161 +0,0 @@
|
|||||||
/*
|
|
||||||
* This is used to extract one children's worth of events from a stream of navigation action events
|
|
||||||
*
|
|
||||||
* Based on the 'action' events that get fired for this navigation state, this utility will fire
|
|
||||||
* focus and blur events for this child
|
|
||||||
*/
|
|
||||||
export default function getChildEventSubscriber(addListener, key) {
|
|
||||||
const actionSubscribers = new Set();
|
|
||||||
const willFocusSubscribers = new Set();
|
|
||||||
const didFocusSubscribers = new Set();
|
|
||||||
const willBlurSubscribers = new Set();
|
|
||||||
const didBlurSubscribers = new Set();
|
|
||||||
|
|
||||||
const removeAll = () => {
|
|
||||||
[
|
|
||||||
actionSubscribers,
|
|
||||||
willFocusSubscribers,
|
|
||||||
didFocusSubscribers,
|
|
||||||
willBlurSubscribers,
|
|
||||||
didBlurSubscribers,
|
|
||||||
].forEach(set => set.clear());
|
|
||||||
|
|
||||||
upstreamSubscribers.forEach(subs => subs && subs.remove());
|
|
||||||
};
|
|
||||||
|
|
||||||
const getChildSubscribers = evtName => {
|
|
||||||
switch (evtName) {
|
|
||||||
case 'action':
|
|
||||||
return actionSubscribers;
|
|
||||||
case 'willFocus':
|
|
||||||
return willFocusSubscribers;
|
|
||||||
case 'didFocus':
|
|
||||||
return didFocusSubscribers;
|
|
||||||
case 'willBlur':
|
|
||||||
return willBlurSubscribers;
|
|
||||||
case 'didBlur':
|
|
||||||
return didBlurSubscribers;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const emit = (type, payload) => {
|
|
||||||
const payloadWithType = { ...payload, type };
|
|
||||||
const subscribers = getChildSubscribers(type);
|
|
||||||
subscribers &&
|
|
||||||
subscribers.forEach(subs => {
|
|
||||||
subs(payloadWithType);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// lastEmittedEvent keeps track of focus state for one route. First we assume
|
|
||||||
// we are blurred. If we are focused on initialization, the first 'action'
|
|
||||||
// event will cause onFocus+willFocus events because we had previously been
|
|
||||||
// considered blurred
|
|
||||||
let lastEmittedEvent = 'didBlur';
|
|
||||||
|
|
||||||
const upstreamEvents = [
|
|
||||||
'willFocus',
|
|
||||||
'didFocus',
|
|
||||||
'willBlur',
|
|
||||||
'didBlur',
|
|
||||||
'action',
|
|
||||||
];
|
|
||||||
|
|
||||||
const upstreamSubscribers = upstreamEvents.map(eventName =>
|
|
||||||
addListener(eventName, payload => {
|
|
||||||
const { state, lastState, action } = payload;
|
|
||||||
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 isChildFocused = focusKey === key;
|
|
||||||
const lastRoute =
|
|
||||||
lastRoutes && lastRoutes.find(route => route.key === key);
|
|
||||||
const newRoute = routes && routes.find(route => route.key === key);
|
|
||||||
const childPayload = {
|
|
||||||
context: `${key}:${action.type}_${payload.context || 'Root'}`,
|
|
||||||
state: newRoute,
|
|
||||||
lastState: lastRoute,
|
|
||||||
action,
|
|
||||||
type: eventName,
|
|
||||||
};
|
|
||||||
const isTransitioning = !!state && state.isTransitioning;
|
|
||||||
|
|
||||||
const previouslyLastEmittedEvent = lastEmittedEvent;
|
|
||||||
|
|
||||||
if (lastEmittedEvent === 'didBlur') {
|
|
||||||
// The child is currently blurred. Look for willFocus conditions
|
|
||||||
if (eventName === 'willFocus' && isChildFocused) {
|
|
||||||
emit((lastEmittedEvent = 'willFocus'), childPayload);
|
|
||||||
} else if (eventName === 'action' && isChildFocused) {
|
|
||||||
emit((lastEmittedEvent = 'willFocus'), childPayload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lastEmittedEvent === 'willFocus') {
|
|
||||||
// We are currently mid-focus. Look for didFocus conditions.
|
|
||||||
// If state.isTransitioning is false, this child event happens immediately after willFocus
|
|
||||||
if (eventName === 'didFocus' && isChildFocused && !isTransitioning) {
|
|
||||||
emit((lastEmittedEvent = 'didFocus'), childPayload);
|
|
||||||
} else if (
|
|
||||||
eventName === 'action' &&
|
|
||||||
isChildFocused &&
|
|
||||||
!isTransitioning
|
|
||||||
) {
|
|
||||||
emit((lastEmittedEvent = 'didFocus'), childPayload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastEmittedEvent === 'didFocus') {
|
|
||||||
// The child is currently focused. Look for blurring events
|
|
||||||
if (!isChildFocused) {
|
|
||||||
// The child is no longer focused within this navigation state
|
|
||||||
emit((lastEmittedEvent = 'willBlur'), childPayload);
|
|
||||||
} else if (eventName === 'willBlur') {
|
|
||||||
// The parent is getting a willBlur event
|
|
||||||
emit((lastEmittedEvent = 'willBlur'), childPayload);
|
|
||||||
} else if (
|
|
||||||
eventName === 'action' &&
|
|
||||||
previouslyLastEmittedEvent === 'didFocus'
|
|
||||||
) {
|
|
||||||
// While focused, pass action events to children for grandchildren focus
|
|
||||||
emit('action', childPayload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastEmittedEvent === 'willBlur') {
|
|
||||||
// The child is mid-blur. Wait for transition to end
|
|
||||||
if (eventName === 'action' && !isChildFocused && !isTransitioning) {
|
|
||||||
// The child is done blurring because transitioning is over, or isTransitioning
|
|
||||||
// never began and didBlur fires immediately after willBlur
|
|
||||||
emit((lastEmittedEvent = 'didBlur'), childPayload);
|
|
||||||
} else if (eventName === 'didBlur') {
|
|
||||||
// Pass through the parent didBlur event if it happens
|
|
||||||
emit((lastEmittedEvent = 'didBlur'), childPayload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastEmittedEvent === 'didBlur' && !newRoute) {
|
|
||||||
removeAll();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
addListener(eventName, eventHandler) {
|
|
||||||
const subscribers = getChildSubscribers(eventName);
|
|
||||||
if (!subscribers) {
|
|
||||||
throw new Error(`Invalid event name "${eventName}"`);
|
|
||||||
}
|
|
||||||
subscribers.add(eventHandler);
|
|
||||||
const remove = () => {
|
|
||||||
subscribers.delete(eventHandler);
|
|
||||||
};
|
|
||||||
return { remove };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
import getChildEventSubscriber from './getChildEventSubscriber';
|
|
||||||
import getChildRouter from './getChildRouter';
|
|
||||||
import getNavigationActionCreators from './routers/getNavigationActionCreators';
|
|
||||||
import invariant from './utils/invariant';
|
|
||||||
|
|
||||||
const createParamGetter = route => (paramName, defaultValue) => {
|
|
||||||
const params = route.params;
|
|
||||||
|
|
||||||
if (params && paramName in params) {
|
|
||||||
return params[paramName];
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
|
|
||||||
const children =
|
|
||||||
navigation._childrenNavigation || (navigation._childrenNavigation = {});
|
|
||||||
|
|
||||||
const childRoute = navigation.state.routes.find(r => r.key === childKey);
|
|
||||||
|
|
||||||
if (!childRoute) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (children[childKey] && children[childKey].state === childRoute) {
|
|
||||||
return children[childKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
const childRouter = getChildRouter(navigation.router, childRoute.routeName);
|
|
||||||
|
|
||||||
// If the route has children, we'll use this to pass in to the action creators
|
|
||||||
// for the childRouter so that any action that depends on the active route will
|
|
||||||
// behave as expected. We don't explicitly require that routers implement routes
|
|
||||||
// and index properties, but if we did then we would put an invariant here to
|
|
||||||
// ensure that a focusedGrandChildRoute exists if childRouter is defined.
|
|
||||||
const focusedGrandChildRoute =
|
|
||||||
childRoute.routes && typeof childRoute.index === 'number'
|
|
||||||
? childRoute.routes[childRoute.index]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const actionCreators = {
|
|
||||||
...navigation.actions,
|
|
||||||
...navigation.router.getActionCreators(childRoute, navigation.state.key),
|
|
||||||
...(childRouter
|
|
||||||
? childRouter.getActionCreators(focusedGrandChildRoute, childRoute.key)
|
|
||||||
: {}),
|
|
||||||
...getNavigationActionCreators(childRoute),
|
|
||||||
};
|
|
||||||
|
|
||||||
const actionHelpers = {};
|
|
||||||
Object.keys(actionCreators).forEach(actionName => {
|
|
||||||
actionHelpers[actionName] = (...args) => {
|
|
||||||
const actionCreator = actionCreators[actionName];
|
|
||||||
const action = actionCreator(...args);
|
|
||||||
return navigation.dispatch(action);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
if (children[childKey]) {
|
|
||||||
children[childKey] = {
|
|
||||||
...children[childKey],
|
|
||||||
...actionHelpers,
|
|
||||||
state: childRoute,
|
|
||||||
router: childRouter,
|
|
||||||
actions: actionCreators,
|
|
||||||
getParam: createParamGetter(childRoute),
|
|
||||||
};
|
|
||||||
return children[childKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
const childSubscriber = getChildEventSubscriber(
|
|
||||||
navigation.addListener,
|
|
||||||
childKey
|
|
||||||
);
|
|
||||||
|
|
||||||
children[childKey] = {
|
|
||||||
...actionHelpers,
|
|
||||||
|
|
||||||
state: childRoute,
|
|
||||||
router: childRouter,
|
|
||||||
actions: actionCreators,
|
|
||||||
getParam: createParamGetter(childRoute),
|
|
||||||
|
|
||||||
getChildNavigation: grandChildKey =>
|
|
||||||
getChildNavigation(children[childKey], grandChildKey, () => {
|
|
||||||
const nav = getCurrentParentNavigation();
|
|
||||||
return nav && nav.getChildNavigation(childKey);
|
|
||||||
}),
|
|
||||||
|
|
||||||
isFocused: () => {
|
|
||||||
const currentNavigation = getCurrentParentNavigation();
|
|
||||||
if (!currentNavigation) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const { routes, index } = currentNavigation.state;
|
|
||||||
if (!currentNavigation.isFocused()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (routes[index].key === childKey) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
dispatch: navigation.dispatch,
|
|
||||||
getScreenProps: navigation.getScreenProps,
|
|
||||||
dangerouslyGetParent: getCurrentParentNavigation,
|
|
||||||
addListener: childSubscriber.addListener,
|
|
||||||
};
|
|
||||||
return children[childKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default getChildNavigation;
|
|
@ -1,9 +0,0 @@
|
|||||||
export default function getChildRouter(router, routeName) {
|
|
||||||
if (router.childRouters && router.childRouters[routeName]) {
|
|
||||||
return router.childRouters[routeName];
|
|
||||||
}
|
|
||||||
|
|
||||||
const Component = router.getComponentForRouteName(routeName);
|
|
||||||
|
|
||||||
return Component.router;
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
import getNavigationActionCreators from './routers/getNavigationActionCreators';
|
|
||||||
import getChildNavigation from './getChildNavigation';
|
|
||||||
|
|
||||||
export default function getNavigation(
|
|
||||||
router,
|
|
||||||
state,
|
|
||||||
dispatch,
|
|
||||||
actionSubscribers,
|
|
||||||
getScreenProps,
|
|
||||||
getCurrentNavigation
|
|
||||||
) {
|
|
||||||
const actions = router.getActionCreators(state, null);
|
|
||||||
|
|
||||||
const navigation = {
|
|
||||||
actions,
|
|
||||||
router,
|
|
||||||
state,
|
|
||||||
dispatch,
|
|
||||||
getScreenProps,
|
|
||||||
getChildNavigation: childKey =>
|
|
||||||
getChildNavigation(navigation, childKey, getCurrentNavigation),
|
|
||||||
isFocused: childKey => {
|
|
||||||
const { routes, index } = getCurrentNavigation().state;
|
|
||||||
if (childKey == null || routes[index].key === childKey) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
addListener: (eventName, handler) => {
|
|
||||||
if (eventName !== 'action') {
|
|
||||||
return { remove: () => {} };
|
|
||||||
}
|
|
||||||
actionSubscribers.add(handler);
|
|
||||||
return {
|
|
||||||
remove: () => {
|
|
||||||
actionSubscribers.delete(handler);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
dangerouslyGetParent: () => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const actionCreators = {
|
|
||||||
...getNavigationActionCreators(navigation.state),
|
|
||||||
...actions,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.keys(actionCreators).forEach(actionName => {
|
|
||||||
navigation[actionName] = (...args) =>
|
|
||||||
navigation.dispatch(actionCreators[actionName](...args));
|
|
||||||
});
|
|
||||||
|
|
||||||
return navigation;
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import createNavigationContainer from '../createNavigationContainer';
|
import createNavigationContainer from '../createNavigationContainer';
|
||||||
import createSwitchNavigator from './createSwitchNavigator';
|
import { createSwitchNavigator } from '@react-navigation/core';
|
||||||
|
|
||||||
const SwitchNavigator = (routeConfigs, config = {}) => {
|
const SwitchNavigator = (routeConfigs, config = {}) => {
|
||||||
const navigator = createSwitchNavigator(routeConfigs, config);
|
const navigator = createSwitchNavigator(routeConfigs, config);
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { polyfill } from 'react-lifecycles-compat';
|
|
||||||
|
|
||||||
function createNavigator(NavigatorView, router, navigationConfig) {
|
|
||||||
class Navigator extends React.Component {
|
|
||||||
static router = router;
|
|
||||||
static navigationOptions = navigationConfig.navigationOptions;
|
|
||||||
|
|
||||||
state = {
|
|
||||||
descriptors: {},
|
|
||||||
screenProps: this.props.screenProps,
|
|
||||||
};
|
|
||||||
|
|
||||||
static getDerivedStateFromProps(nextProps, prevState) {
|
|
||||||
const prevDescriptors = prevState.descriptors;
|
|
||||||
const { navigation, screenProps } = nextProps;
|
|
||||||
const { state } = navigation;
|
|
||||||
const { routes } = state;
|
|
||||||
if (typeof routes === 'undefined') {
|
|
||||||
throw new TypeError(
|
|
||||||
'No "routes" found in navigation state. Did you try to pass the navigation prop of a React component to a Navigator child? See https://reactnavigation.org/docs/en/custom-navigators.html#navigator-navigation-prop'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const descriptors = {};
|
|
||||||
|
|
||||||
routes.forEach(route => {
|
|
||||||
if (
|
|
||||||
prevDescriptors &&
|
|
||||||
prevDescriptors[route.key] &&
|
|
||||||
route === prevDescriptors[route.key].state &&
|
|
||||||
screenProps === prevState.screenProps
|
|
||||||
) {
|
|
||||||
descriptors[route.key] = prevDescriptors[route.key];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const getComponent = router.getComponentForRouteName.bind(
|
|
||||||
null,
|
|
||||||
route.routeName
|
|
||||||
);
|
|
||||||
const childNavigation = navigation.getChildNavigation(route.key);
|
|
||||||
const options = router.getScreenOptions(childNavigation, screenProps);
|
|
||||||
descriptors[route.key] = {
|
|
||||||
key: route.key,
|
|
||||||
getComponent,
|
|
||||||
options,
|
|
||||||
state: route,
|
|
||||||
navigation: childNavigation,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return { descriptors, screenProps };
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<NavigatorView
|
|
||||||
{...this.props}
|
|
||||||
screenProps={this.state.screenProps}
|
|
||||||
navigation={this.props.navigation}
|
|
||||||
navigationConfig={navigationConfig}
|
|
||||||
descriptors={this.state.descriptors}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return polyfill(Navigator);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createNavigator;
|
|
@ -1,11 +0,0 @@
|
|||||||
import createNavigator from '../navigators/createNavigator';
|
|
||||||
import SwitchRouter from '../routers/SwitchRouter';
|
|
||||||
import SwitchView from '../views/SwitchView/SwitchView';
|
|
||||||
|
|
||||||
function createSwitchNavigator(routeConfigMap, switchConfig = {}) {
|
|
||||||
const router = SwitchRouter(routeConfigMap, switchConfig);
|
|
||||||
const Navigator = createNavigator(SwitchView, router, switchConfig);
|
|
||||||
return Navigator;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createSwitchNavigator;
|
|
40
src/react-navigation.js
vendored
@ -6,24 +6,24 @@ module.exports = {
|
|||||||
return require('./createNavigationContainer').default;
|
return require('./createNavigationContainer').default;
|
||||||
},
|
},
|
||||||
get StateUtils() {
|
get StateUtils() {
|
||||||
return require('./StateUtils').default;
|
return require('@react-navigation/core').StateUtils;
|
||||||
},
|
},
|
||||||
get getNavigation() {
|
get getNavigation() {
|
||||||
return require('./getNavigation').default;
|
return require('@react-navigation/core').getNavigation;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Navigators
|
// Navigators
|
||||||
get createNavigator() {
|
get createNavigator() {
|
||||||
return require('./navigators/createNavigator').default;
|
return require('@react-navigation/core').createNavigator;
|
||||||
},
|
},
|
||||||
get createKeyboardAwareNavigator() {
|
get createKeyboardAwareNavigator() {
|
||||||
return require('./navigators/createKeyboardAwareNavigator').default;
|
return require('./navigators/createKeyboardAwareNavigator').default;
|
||||||
},
|
},
|
||||||
get NavigationProvider() {
|
get NavigationProvider() {
|
||||||
return require('./views/NavigationContext').default.NavigationProvider;
|
return require('@react-navigation/core').NavigationProvider;
|
||||||
},
|
},
|
||||||
get NavigationConsumer() {
|
get NavigationConsumer() {
|
||||||
return require('./views/NavigationContext').default.NavigationConsumer;
|
return require('@react-navigation/core').NavigationConsumer;
|
||||||
},
|
},
|
||||||
get createStackNavigator() {
|
get createStackNavigator() {
|
||||||
return require('react-navigation-stack').createStackNavigator;
|
return require('react-navigation-stack').createStackNavigator;
|
||||||
@ -75,10 +75,10 @@ module.exports = {
|
|||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
get NavigationActions() {
|
get NavigationActions() {
|
||||||
return require('./NavigationActions').default;
|
return require('@react-navigation/core').NavigationActions;
|
||||||
},
|
},
|
||||||
get StackActions() {
|
get StackActions() {
|
||||||
return require('./routers/StackActions').default;
|
return require('@react-navigation/core').StackActions;
|
||||||
},
|
},
|
||||||
get DrawerActions() {
|
get DrawerActions() {
|
||||||
return require('react-navigation-drawer').DrawerActions;
|
return require('react-navigation-drawer').DrawerActions;
|
||||||
@ -86,33 +86,33 @@ module.exports = {
|
|||||||
|
|
||||||
// Routers
|
// Routers
|
||||||
get StackRouter() {
|
get StackRouter() {
|
||||||
return require('./routers/StackRouter').default;
|
return require('@react-navigation/core').StackRouter;
|
||||||
},
|
},
|
||||||
get TabRouter() {
|
get TabRouter() {
|
||||||
return require('./routers/TabRouter').default;
|
return require('@react-navigation/core').TabRouter;
|
||||||
},
|
},
|
||||||
get DrawerRouter() {
|
get DrawerRouter() {
|
||||||
return require('react-navigation-drawer').DrawerRouter;
|
return require('react-navigation-drawer').DrawerRouter;
|
||||||
},
|
},
|
||||||
get SwitchRouter() {
|
get SwitchRouter() {
|
||||||
return require('./routers/SwitchRouter').default;
|
return require('@react-navigation/core').SwitchRouter;
|
||||||
},
|
},
|
||||||
get createConfigGetter() {
|
get createConfigGetter() {
|
||||||
return require('./routers/createConfigGetter').default;
|
return require('@react-navigation/core').StackAcreateConfigGetterctions;
|
||||||
},
|
},
|
||||||
get getScreenForRouteName() {
|
get getScreenForRouteName() {
|
||||||
return require('./routers/getScreenForRouteName').default;
|
return require('@react-navigation/core').getScreenForRouteName;
|
||||||
},
|
},
|
||||||
get validateRouteConfigMap() {
|
get validateRouteConfigMap() {
|
||||||
return require('./routers/validateRouteConfigMap').default;
|
return require('@react-navigation/core').validateRouteConfigMap;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
get getActiveChildNavigationOptions() {
|
get getActiveChildNavigationOptions() {
|
||||||
return require('./utils/getActiveChildNavigationOptions').default;
|
return require('@react-navigation/core').getActiveChildNavigationOptions;
|
||||||
},
|
},
|
||||||
get pathUtils() {
|
get pathUtils() {
|
||||||
return require('./routers/pathUtils').default;
|
return require('@react-navigation/core').pathUtils;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
@ -132,7 +132,7 @@ module.exports = {
|
|||||||
return require('react-native-safe-area-view').default;
|
return require('react-native-safe-area-view').default;
|
||||||
},
|
},
|
||||||
get SceneView() {
|
get SceneView() {
|
||||||
return require('./views/SceneView').default;
|
return require('@react-navigation/core').SceneView;
|
||||||
},
|
},
|
||||||
get ResourceSavingSceneView() {
|
get ResourceSavingSceneView() {
|
||||||
return require('./views/ResourceSavingSceneView').default;
|
return require('./views/ResourceSavingSceneView').default;
|
||||||
@ -185,20 +185,20 @@ module.exports = {
|
|||||||
|
|
||||||
// SwitchView
|
// SwitchView
|
||||||
get SwitchView() {
|
get SwitchView() {
|
||||||
return require('./views/SwitchView/SwitchView').default;
|
return require('@react-navigation/core').SwitchView;
|
||||||
},
|
},
|
||||||
|
|
||||||
// NavigationEvents
|
// NavigationEvents
|
||||||
get NavigationEvents() {
|
get NavigationEvents() {
|
||||||
return require('./views/NavigationEvents').default;
|
return require('@react-navigation/core').NavigationEvents;
|
||||||
},
|
},
|
||||||
|
|
||||||
// HOCs
|
// HOCs
|
||||||
get withNavigation() {
|
get withNavigation() {
|
||||||
return require('./views/withNavigation').default;
|
return require('@react-navigation/core').withNavigation;
|
||||||
},
|
},
|
||||||
get withNavigationFocus() {
|
get withNavigationFocus() {
|
||||||
return require('./views/withNavigationFocus').default;
|
return require('@react-navigation/core').withNavigationFocus;
|
||||||
},
|
},
|
||||||
get withOrientation() {
|
get withOrientation() {
|
||||||
return require('./views/withOrientation').default;
|
return require('./views/withOrientation').default;
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
/* eslint global-require: 0 */
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
// Core
|
|
||||||
get createNavigationContainer() {
|
|
||||||
return require('./createNavigationContainer').default;
|
|
||||||
},
|
|
||||||
get StateUtils() {
|
|
||||||
return require('./StateUtils').default;
|
|
||||||
},
|
|
||||||
get getNavigation() {
|
|
||||||
return require('./getNavigation').default;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Navigators
|
|
||||||
get createNavigator() {
|
|
||||||
return require('./navigators/createNavigator').default;
|
|
||||||
},
|
|
||||||
get createSwitchNavigator() {
|
|
||||||
return require('./navigators/createSwitchNavigator').default;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
get NavigationActions() {
|
|
||||||
return require('./NavigationActions').default;
|
|
||||||
},
|
|
||||||
get StackActions() {
|
|
||||||
return require('./routers/StackActions').default;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Routers
|
|
||||||
get StackRouter() {
|
|
||||||
return require('./routers/StackRouter').default;
|
|
||||||
},
|
|
||||||
get TabRouter() {
|
|
||||||
return require('./routers/TabRouter').default;
|
|
||||||
},
|
|
||||||
get SwitchRouter() {
|
|
||||||
return require('./routers/SwitchRouter').default;
|
|
||||||
},
|
|
||||||
|
|
||||||
// NavigationEvents
|
|
||||||
get NavigationEvents() {
|
|
||||||
return require('./views/NavigationEvents').default;
|
|
||||||
},
|
|
||||||
|
|
||||||
// HOCs
|
|
||||||
get withNavigation() {
|
|
||||||
return require('./views/withNavigation').default;
|
|
||||||
},
|
|
||||||
get withNavigationFocus() {
|
|
||||||
return require('./views/withNavigationFocus').default;
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,11 +0,0 @@
|
|||||||
let uniqueBaseId = `id-${Date.now()}`;
|
|
||||||
let uuidCount = 0;
|
|
||||||
|
|
||||||
export function _TESTING_ONLY_normalize_keys() {
|
|
||||||
uniqueBaseId = 'id';
|
|
||||||
uuidCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateKey() {
|
|
||||||
return `${uniqueBaseId}-${uuidCount++}`;
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
const POP = 'Navigation/POP';
|
|
||||||
const POP_TO_TOP = 'Navigation/POP_TO_TOP';
|
|
||||||
const PUSH = 'Navigation/PUSH';
|
|
||||||
const RESET = 'Navigation/RESET';
|
|
||||||
const REPLACE = 'Navigation/REPLACE';
|
|
||||||
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
|
|
||||||
|
|
||||||
const pop = payload => ({
|
|
||||||
type: POP,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const popToTop = payload => ({
|
|
||||||
type: POP_TO_TOP,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const push = payload => ({
|
|
||||||
type: PUSH,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const reset = payload => ({
|
|
||||||
type: RESET,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const replace = payload => ({
|
|
||||||
type: REPLACE,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const completeTransition = payload => ({
|
|
||||||
type: COMPLETE_TRANSITION,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
POP,
|
|
||||||
POP_TO_TOP,
|
|
||||||
PUSH,
|
|
||||||
RESET,
|
|
||||||
REPLACE,
|
|
||||||
COMPLETE_TRANSITION,
|
|
||||||
|
|
||||||
pop,
|
|
||||||
popToTop,
|
|
||||||
push,
|
|
||||||
reset,
|
|
||||||
replace,
|
|
||||||
completeTransition,
|
|
||||||
};
|
|
@ -1,572 +0,0 @@
|
|||||||
import NavigationActions from '../NavigationActions';
|
|
||||||
import StackActions from './StackActions';
|
|
||||||
import createConfigGetter from './createConfigGetter';
|
|
||||||
import getScreenForRouteName from './getScreenForRouteName';
|
|
||||||
import StateUtils from '../StateUtils';
|
|
||||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
|
||||||
import invariant from '../utils/invariant';
|
|
||||||
import { generateKey } from './KeyGenerator';
|
|
||||||
import { createPathParser } from './pathUtils';
|
|
||||||
|
|
||||||
function behavesLikePushAction(action) {
|
|
||||||
return (
|
|
||||||
action.type === NavigationActions.NAVIGATE ||
|
|
||||||
action.type === StackActions.PUSH
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultActionCreators = (route, navStateKey) => ({});
|
|
||||||
|
|
||||||
function isResetToRootStack(action) {
|
|
||||||
return action.type === StackActions.RESET && action.key === null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (routeConfigs, stackConfig = {}) => {
|
|
||||||
// Fail fast on invalid route definitions
|
|
||||||
validateRouteConfigMap(routeConfigs);
|
|
||||||
|
|
||||||
const childRouters = {};
|
|
||||||
const routeNames = Object.keys(routeConfigs);
|
|
||||||
|
|
||||||
// Loop through routes and find child routers
|
|
||||||
routeNames.forEach(routeName => {
|
|
||||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
|
||||||
if (screen && screen.router) {
|
|
||||||
// If it has a router it's a navigator.
|
|
||||||
childRouters[routeName] = screen.router;
|
|
||||||
} else {
|
|
||||||
// If it doesn't have router it's an ordinary React component.
|
|
||||||
childRouters[routeName] = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { initialRouteParams } = stackConfig;
|
|
||||||
const getCustomActionCreators =
|
|
||||||
stackConfig.getCustomActionCreators || defaultActionCreators;
|
|
||||||
|
|
||||||
const initialRouteName = stackConfig.initialRouteName || routeNames[0];
|
|
||||||
|
|
||||||
const initialChildRouter = childRouters[initialRouteName];
|
|
||||||
|
|
||||||
function getInitialState(action) {
|
|
||||||
let route = {};
|
|
||||||
const childRouter = childRouters[action.routeName];
|
|
||||||
|
|
||||||
// This is a push-like action, and childRouter will be a router or null if we are responsible for this routeName
|
|
||||||
if (behavesLikePushAction(action) && childRouter !== undefined) {
|
|
||||||
let childState = {};
|
|
||||||
// The router is null for normal leaf routes
|
|
||||||
if (childRouter !== null) {
|
|
||||||
const childAction =
|
|
||||||
action.action || NavigationActions.init({ params: action.params });
|
|
||||||
childState = childRouter.getStateForAction(childAction);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
key: 'StackRouterRoot',
|
|
||||||
isTransitioning: false,
|
|
||||||
index: 0,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
params: action.params,
|
|
||||||
...childState,
|
|
||||||
key: action.key || generateKey(),
|
|
||||||
routeName: action.routeName,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialChildRouter) {
|
|
||||||
route = initialChildRouter.getStateForAction(
|
|
||||||
NavigationActions.navigate({
|
|
||||||
routeName: initialRouteName,
|
|
||||||
params: initialRouteParams,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const params = (route.params || action.params || initialRouteParams) && {
|
|
||||||
...(route.params || {}),
|
|
||||||
...(action.params || {}),
|
|
||||||
...(initialRouteParams || {}),
|
|
||||||
};
|
|
||||||
const { initialRouteKey } = stackConfig;
|
|
||||||
route = {
|
|
||||||
...route,
|
|
||||||
...(params ? { params } : {}),
|
|
||||||
routeName: initialRouteName,
|
|
||||||
key: action.key || (initialRouteKey || generateKey()),
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
key: 'StackRouterRoot',
|
|
||||||
isTransitioning: false,
|
|
||||||
index: 0,
|
|
||||||
routes: [route],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
getPathAndParamsForRoute,
|
|
||||||
getActionForPathAndParams,
|
|
||||||
} = createPathParser(childRouters, routeConfigs, stackConfig);
|
|
||||||
|
|
||||||
return {
|
|
||||||
childRouters,
|
|
||||||
|
|
||||||
getComponentForState(state) {
|
|
||||||
const activeChildRoute = state.routes[state.index];
|
|
||||||
const { routeName } = activeChildRoute;
|
|
||||||
if (childRouters[routeName]) {
|
|
||||||
return childRouters[routeName].getComponentForState(activeChildRoute);
|
|
||||||
}
|
|
||||||
return getScreenForRouteName(routeConfigs, routeName);
|
|
||||||
},
|
|
||||||
|
|
||||||
getComponentForRouteName(routeName) {
|
|
||||||
return getScreenForRouteName(routeConfigs, routeName);
|
|
||||||
},
|
|
||||||
|
|
||||||
getActionCreators(route, navStateKey) {
|
|
||||||
return {
|
|
||||||
...getCustomActionCreators(route, navStateKey),
|
|
||||||
pop: (n, params) =>
|
|
||||||
StackActions.pop({
|
|
||||||
n,
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
popToTop: params => StackActions.popToTop(params),
|
|
||||||
push: (routeName, params, action) =>
|
|
||||||
StackActions.push({
|
|
||||||
routeName,
|
|
||||||
params,
|
|
||||||
action,
|
|
||||||
}),
|
|
||||||
replace: (replaceWith, params, action, newKey) => {
|
|
||||||
if (typeof replaceWith === 'string') {
|
|
||||||
return StackActions.replace({
|
|
||||||
routeName: replaceWith,
|
|
||||||
params,
|
|
||||||
action,
|
|
||||||
key: route.key,
|
|
||||||
newKey,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
invariant(
|
|
||||||
typeof replaceWith === 'object',
|
|
||||||
'Must replaceWith an object or a string'
|
|
||||||
);
|
|
||||||
invariant(
|
|
||||||
params == null,
|
|
||||||
'Params must not be provided to .replace() when specifying an object'
|
|
||||||
);
|
|
||||||
invariant(
|
|
||||||
action == null,
|
|
||||||
'Child action must not be provided to .replace() when specifying an object'
|
|
||||||
);
|
|
||||||
invariant(
|
|
||||||
newKey == null,
|
|
||||||
'Child action must not be provided to .replace() when specifying an object'
|
|
||||||
);
|
|
||||||
return StackActions.replace(replaceWith);
|
|
||||||
},
|
|
||||||
reset: (actions, index) =>
|
|
||||||
StackActions.reset({
|
|
||||||
actions,
|
|
||||||
index: index == null ? actions.length - 1 : index,
|
|
||||||
key: navStateKey,
|
|
||||||
}),
|
|
||||||
dismiss: () =>
|
|
||||||
NavigationActions.back({
|
|
||||||
key: navStateKey,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getStateForAction(action, state) {
|
|
||||||
// Set up the initial state if needed
|
|
||||||
if (!state) {
|
|
||||||
return getInitialState(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeChildRoute = state.routes[state.index];
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isResetToRootStack(action) &&
|
|
||||||
action.type !== NavigationActions.NAVIGATE
|
|
||||||
) {
|
|
||||||
// Let the active child router handle the action
|
|
||||||
const activeChildRouter = childRouters[activeChildRoute.routeName];
|
|
||||||
if (activeChildRouter) {
|
|
||||||
const route = activeChildRouter.getStateForAction(
|
|
||||||
action,
|
|
||||||
activeChildRoute
|
|
||||||
);
|
|
||||||
if (route !== null && route !== activeChildRoute) {
|
|
||||||
return StateUtils.replaceAt(
|
|
||||||
state,
|
|
||||||
activeChildRoute.key,
|
|
||||||
route,
|
|
||||||
// the following tells replaceAt to NOT change the index to this route for the setParam action, because people don't expect param-setting actions to switch the active route
|
|
||||||
action.type === NavigationActions.SET_PARAMS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (action.type === NavigationActions.NAVIGATE) {
|
|
||||||
// Traverse routes from the top of the stack to the bottom, so the
|
|
||||||
// active route has the first opportunity, then the one before it, etc.
|
|
||||||
for (let childRoute of state.routes.slice().reverse()) {
|
|
||||||
let childRouter = childRouters[childRoute.routeName];
|
|
||||||
let childAction =
|
|
||||||
action.routeName === childRoute.routeName && action.action
|
|
||||||
? action.action
|
|
||||||
: action;
|
|
||||||
|
|
||||||
if (childRouter) {
|
|
||||||
const nextRouteState = childRouter.getStateForAction(
|
|
||||||
childAction,
|
|
||||||
childRoute
|
|
||||||
);
|
|
||||||
|
|
||||||
if (nextRouteState === null || nextRouteState !== childRoute) {
|
|
||||||
const newState = StateUtils.replaceAndPrune(
|
|
||||||
state,
|
|
||||||
nextRouteState ? nextRouteState.key : childRoute.key,
|
|
||||||
nextRouteState ? nextRouteState : childRoute
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...newState,
|
|
||||||
isTransitioning:
|
|
||||||
state.index !== newState.index
|
|
||||||
? action.immediate !== true
|
|
||||||
: state.isTransitioning,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle explicit push navigation action. This must happen after the
|
|
||||||
// focused child router has had a chance to handle the action.
|
|
||||||
if (
|
|
||||||
behavesLikePushAction(action) &&
|
|
||||||
childRouters[action.routeName] !== undefined
|
|
||||||
) {
|
|
||||||
const childRouter = childRouters[action.routeName];
|
|
||||||
let route;
|
|
||||||
|
|
||||||
invariant(
|
|
||||||
action.type !== StackActions.PUSH || action.key == null,
|
|
||||||
'StackRouter does not support key on the push action'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Before pushing a new route we first try to find one in the existing route stack
|
|
||||||
// More information on this: https://github.com/react-navigation/rfcs/blob/master/text/0004-less-pushy-navigate.md
|
|
||||||
const lastRouteIndex = state.routes.findIndex(r => {
|
|
||||||
if (action.key) {
|
|
||||||
return r.key === action.key;
|
|
||||||
} else {
|
|
||||||
return r.routeName === action.routeName;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (action.type !== StackActions.PUSH && lastRouteIndex !== -1) {
|
|
||||||
// If index is unchanged and params are not being set, leave state identity intact
|
|
||||||
if (state.index === lastRouteIndex && !action.params) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the now unused routes at the tail of the routes array
|
|
||||||
const routes = state.routes.slice(0, lastRouteIndex + 1);
|
|
||||||
|
|
||||||
// Apply params if provided, otherwise leave route identity intact
|
|
||||||
if (action.params) {
|
|
||||||
const route = state.routes[lastRouteIndex];
|
|
||||||
routes[lastRouteIndex] = {
|
|
||||||
...route,
|
|
||||||
params: {
|
|
||||||
...route.params,
|
|
||||||
...action.params,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Return state with new index. Change isTransitioning only if index has changed
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isTransitioning:
|
|
||||||
state.index !== lastRouteIndex
|
|
||||||
? action.immediate !== true
|
|
||||||
: state.isTransitioning,
|
|
||||||
index: lastRouteIndex,
|
|
||||||
routes,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (childRouter) {
|
|
||||||
const childAction =
|
|
||||||
action.action || NavigationActions.init({ params: action.params });
|
|
||||||
route = {
|
|
||||||
params: action.params,
|
|
||||||
// merge the child state in this order to allow params override
|
|
||||||
...childRouter.getStateForAction(childAction),
|
|
||||||
routeName: action.routeName,
|
|
||||||
key: action.key || generateKey(),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
route = {
|
|
||||||
params: action.params,
|
|
||||||
routeName: action.routeName,
|
|
||||||
key: action.key || generateKey(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...StateUtils.push(state, route),
|
|
||||||
isTransitioning: action.immediate !== true,
|
|
||||||
};
|
|
||||||
} else if (
|
|
||||||
action.type === StackActions.PUSH &&
|
|
||||||
childRouters[action.routeName] === undefined
|
|
||||||
) {
|
|
||||||
// Return the state identity to bubble the action up
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle navigation to other child routers that are not yet pushed
|
|
||||||
if (behavesLikePushAction(action)) {
|
|
||||||
const childRouterNames = Object.keys(childRouters);
|
|
||||||
for (let i = 0; i < childRouterNames.length; i++) {
|
|
||||||
const childRouterName = childRouterNames[i];
|
|
||||||
const childRouter = childRouters[childRouterName];
|
|
||||||
if (childRouter) {
|
|
||||||
// For each child router, start with a blank state
|
|
||||||
const initChildRoute = childRouter.getStateForAction(
|
|
||||||
NavigationActions.init()
|
|
||||||
);
|
|
||||||
// Then check to see if the router handles our navigate action
|
|
||||||
const navigatedChildRoute = childRouter.getStateForAction(
|
|
||||||
action,
|
|
||||||
initChildRoute
|
|
||||||
);
|
|
||||||
let routeToPush = null;
|
|
||||||
if (navigatedChildRoute === null) {
|
|
||||||
// Push the route if the router has 'handled' the action and returned null
|
|
||||||
routeToPush = initChildRoute;
|
|
||||||
} else if (navigatedChildRoute !== initChildRoute) {
|
|
||||||
// Push the route if the state has changed in response to this navigation
|
|
||||||
routeToPush = navigatedChildRoute;
|
|
||||||
}
|
|
||||||
if (routeToPush) {
|
|
||||||
const route = {
|
|
||||||
...routeToPush,
|
|
||||||
routeName: childRouterName,
|
|
||||||
key: action.key || generateKey(),
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
...StateUtils.push(state, route),
|
|
||||||
isTransitioning: action.immediate !== true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pop-to-top behavior. Make sure this happens after children have had a chance to handle the action, so that the inner stack pops to top first.
|
|
||||||
if (action.type === StackActions.POP_TO_TOP) {
|
|
||||||
// Refuse to handle pop to top if a key is given that doesn't correspond
|
|
||||||
// to this router
|
|
||||||
if (action.key && state.key !== action.key) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're already at the top, then we return the state with a new
|
|
||||||
// identity so that the action is handled by this router.
|
|
||||||
if (state.index > 0) {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isTransitioning: action.immediate !== true,
|
|
||||||
index: 0,
|
|
||||||
routes: [state.routes[0]],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle replace action
|
|
||||||
if (action.type === StackActions.REPLACE) {
|
|
||||||
let routeIndex;
|
|
||||||
|
|
||||||
// If the key param is undefined, set the index to the last route in the stack
|
|
||||||
if (action.key === undefined && state.routes.length) {
|
|
||||||
routeIndex = state.routes.length - 1;
|
|
||||||
} else {
|
|
||||||
routeIndex = state.routes.findIndex(r => r.key === action.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only replace if the key matches one of our routes
|
|
||||||
if (routeIndex !== -1) {
|
|
||||||
const childRouter = childRouters[action.routeName];
|
|
||||||
let childState = {};
|
|
||||||
if (childRouter) {
|
|
||||||
const childAction =
|
|
||||||
action.action ||
|
|
||||||
NavigationActions.init({ params: action.params });
|
|
||||||
childState = childRouter.getStateForAction(childAction);
|
|
||||||
}
|
|
||||||
const routes = [...state.routes];
|
|
||||||
routes[routeIndex] = {
|
|
||||||
params: action.params,
|
|
||||||
// merge the child state in this order to allow params override
|
|
||||||
...childState,
|
|
||||||
routeName: action.routeName,
|
|
||||||
key: action.newKey || generateKey(),
|
|
||||||
};
|
|
||||||
return { ...state, routes };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update transitioning state
|
|
||||||
if (
|
|
||||||
action.type === StackActions.COMPLETE_TRANSITION &&
|
|
||||||
(action.key == null || action.key === state.key) &&
|
|
||||||
state.isTransitioning
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.type === NavigationActions.SET_PARAMS) {
|
|
||||||
const key = action.key;
|
|
||||||
const lastRoute = state.routes.find(route => route.key === key);
|
|
||||||
if (lastRoute) {
|
|
||||||
const params = {
|
|
||||||
...lastRoute.params,
|
|
||||||
...action.params,
|
|
||||||
};
|
|
||||||
const routes = [...state.routes];
|
|
||||||
routes[state.routes.indexOf(lastRoute)] = {
|
|
||||||
...lastRoute,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
routes,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.type === StackActions.RESET) {
|
|
||||||
// Only handle reset actions that are unspecified or match this state key
|
|
||||||
if (action.key != null && action.key != state.key) {
|
|
||||||
// Deliberately use != instead of !== so we can match null with
|
|
||||||
// undefined on either the state or the action
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
const newStackActions = action.actions;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
routes: newStackActions.map(newStackAction => {
|
|
||||||
const router = childRouters[newStackAction.routeName];
|
|
||||||
|
|
||||||
let childState = {};
|
|
||||||
|
|
||||||
if (router) {
|
|
||||||
const childAction =
|
|
||||||
newStackAction.action ||
|
|
||||||
NavigationActions.init({ params: newStackAction.params });
|
|
||||||
|
|
||||||
childState = router.getStateForAction(childAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
params: newStackAction.params,
|
|
||||||
...childState,
|
|
||||||
routeName: newStackAction.routeName,
|
|
||||||
key: newStackAction.key || generateKey(),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
index: action.index,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
action.type === NavigationActions.BACK ||
|
|
||||||
action.type === StackActions.POP
|
|
||||||
) {
|
|
||||||
const { key, n, immediate } = action;
|
|
||||||
let backRouteIndex = state.index;
|
|
||||||
if (action.type === StackActions.POP && n != null) {
|
|
||||||
// determine the index to go back *from*. In this case, n=1 means to go
|
|
||||||
// back from state.index, as if it were a normal "BACK" action
|
|
||||||
backRouteIndex = Math.max(1, state.index - n + 1);
|
|
||||||
} else if (key) {
|
|
||||||
const backRoute = state.routes.find(route => route.key === key);
|
|
||||||
backRouteIndex = state.routes.indexOf(backRoute);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backRouteIndex > 0) {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
routes: state.routes.slice(0, backRouteIndex),
|
|
||||||
index: backRouteIndex - 1,
|
|
||||||
isTransitioning: immediate !== true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// By this point in the router's state handling logic, we have handled the behavior of the active route, and handled any stack actions.
|
|
||||||
// If we haven't returned by now, we should allow non-active child routers to handle this action, and switch to that index if the child state (route) does change..
|
|
||||||
|
|
||||||
const keyIndex = action.key ? StateUtils.indexOf(state, action.key) : -1;
|
|
||||||
|
|
||||||
// Traverse routes from the top of the stack to the bottom, so the
|
|
||||||
// active route has the first opportunity, then the one before it, etc.
|
|
||||||
for (let childRoute of state.routes.slice().reverse()) {
|
|
||||||
if (childRoute.key === activeChildRoute.key) {
|
|
||||||
// skip over the active child because we let it attempt to handle the action earlier
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// If a key is provided and in routes state then let's use that
|
|
||||||
// knowledge to skip extra getStateForAction calls on other child
|
|
||||||
// routers
|
|
||||||
if (keyIndex >= 0 && childRoute.key !== action.key) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let childRouter = childRouters[childRoute.routeName];
|
|
||||||
if (childRouter) {
|
|
||||||
const route = childRouter.getStateForAction(action, childRoute);
|
|
||||||
|
|
||||||
if (route === null) {
|
|
||||||
return state;
|
|
||||||
} else if (route && route !== childRoute) {
|
|
||||||
return StateUtils.replaceAt(
|
|
||||||
state,
|
|
||||||
childRoute.key,
|
|
||||||
route,
|
|
||||||
// the following tells replaceAt to NOT change the index to this route for the setParam action or complete transition action,
|
|
||||||
// because people don't expect these actions to switch the active route
|
|
||||||
action.type === NavigationActions.SET_PARAMS ||
|
|
||||||
action.type === StackActions.COMPLETE_TRANSITION
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
|
||||||
},
|
|
||||||
|
|
||||||
getPathAndParamsForState(state) {
|
|
||||||
const route = state.routes[state.index];
|
|
||||||
return getPathAndParamsForRoute(route);
|
|
||||||
},
|
|
||||||
|
|
||||||
getActionForPathAndParams(path, params) {
|
|
||||||
return getActionForPathAndParams(path, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
getScreenOptions: createConfigGetter(
|
|
||||||
routeConfigs,
|
|
||||||
stackConfig.defaultNavigationOptions
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,327 +0,0 @@
|
|||||||
import invariant from '../utils/invariant';
|
|
||||||
import getScreenForRouteName from './getScreenForRouteName';
|
|
||||||
import createConfigGetter from './createConfigGetter';
|
|
||||||
|
|
||||||
import NavigationActions from '../NavigationActions';
|
|
||||||
import StackActions from './StackActions';
|
|
||||||
import validateRouteConfigMap from './validateRouteConfigMap';
|
|
||||||
import { createPathParser } from './pathUtils';
|
|
||||||
|
|
||||||
const defaultActionCreators = (route, navStateKey) => ({});
|
|
||||||
|
|
||||||
function childrenUpdateWithoutSwitchingIndex(actionType) {
|
|
||||||
return [
|
|
||||||
NavigationActions.SET_PARAMS,
|
|
||||||
// Todo: make SwitchRouter not depend on StackActions..
|
|
||||||
StackActions.COMPLETE_TRANSITION,
|
|
||||||
].includes(actionType);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (routeConfigs, config = {}) => {
|
|
||||||
// Fail fast on invalid route definitions
|
|
||||||
validateRouteConfigMap(routeConfigs);
|
|
||||||
|
|
||||||
const order = config.order || Object.keys(routeConfigs);
|
|
||||||
|
|
||||||
const getCustomActionCreators =
|
|
||||||
config.getCustomActionCreators || defaultActionCreators;
|
|
||||||
|
|
||||||
const initialRouteParams = config.initialRouteParams;
|
|
||||||
const initialRouteName = config.initialRouteName || order[0];
|
|
||||||
const backBehavior = config.backBehavior || 'none';
|
|
||||||
const shouldBackNavigateToInitialRoute = backBehavior === 'initialRoute';
|
|
||||||
const resetOnBlur = config.hasOwnProperty('resetOnBlur')
|
|
||||||
? config.resetOnBlur
|
|
||||||
: true;
|
|
||||||
const initialRouteIndex = order.indexOf(initialRouteName);
|
|
||||||
const childRouters = {};
|
|
||||||
order.forEach(routeName => {
|
|
||||||
const routeConfig = routeConfigs[routeName];
|
|
||||||
childRouters[routeName] = null;
|
|
||||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
|
||||||
if (screen.router) {
|
|
||||||
childRouters[routeName] = screen.router;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
getPathAndParamsForRoute,
|
|
||||||
getActionForPathAndParams,
|
|
||||||
} = createPathParser(childRouters, routeConfigs, config);
|
|
||||||
|
|
||||||
if (initialRouteIndex === -1) {
|
|
||||||
throw new Error(
|
|
||||||
`Invalid initialRouteName '${initialRouteName}'.` +
|
|
||||||
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetChildRoute(routeName) {
|
|
||||||
const params =
|
|
||||||
routeName === initialRouteName ? initialRouteParams : undefined;
|
|
||||||
const childRouter = childRouters[routeName];
|
|
||||||
if (childRouter) {
|
|
||||||
const childAction = NavigationActions.init();
|
|
||||||
return {
|
|
||||||
...childRouter.getStateForAction(childAction),
|
|
||||||
key: routeName,
|
|
||||||
routeName,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
key: routeName,
|
|
||||||
routeName,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNextState(prevState, possibleNextState) {
|
|
||||||
if (!prevState) {
|
|
||||||
return possibleNextState;
|
|
||||||
}
|
|
||||||
|
|
||||||
let nextState;
|
|
||||||
if (prevState.index !== possibleNextState.index && resetOnBlur) {
|
|
||||||
const prevRouteName = prevState.routes[prevState.index].routeName;
|
|
||||||
const nextRoutes = [...possibleNextState.routes];
|
|
||||||
nextRoutes[prevState.index] = resetChildRoute(prevRouteName);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...possibleNextState,
|
|
||||||
routes: nextRoutes,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
nextState = possibleNextState;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextState;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInitialState() {
|
|
||||||
const routes = order.map(resetChildRoute);
|
|
||||||
return {
|
|
||||||
routes,
|
|
||||||
index: initialRouteIndex,
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
childRouters,
|
|
||||||
|
|
||||||
getActionCreators(route, stateKey) {
|
|
||||||
return getCustomActionCreators(route, stateKey);
|
|
||||||
},
|
|
||||||
|
|
||||||
getStateForAction(action, inputState) {
|
|
||||||
let prevState = inputState ? { ...inputState } : inputState;
|
|
||||||
let state = inputState || getInitialState();
|
|
||||||
let activeChildIndex = state.index;
|
|
||||||
|
|
||||||
if (action.type === NavigationActions.INIT) {
|
|
||||||
// NOTE(brentvatne): this seems weird... why are we merging these
|
|
||||||
// params into child routes?
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
// Merge any params from the action into all the child routes
|
|
||||||
const { params } = action;
|
|
||||||
if (params) {
|
|
||||||
state.routes = state.routes.map(route => ({
|
|
||||||
...route,
|
|
||||||
params: {
|
|
||||||
...route.params,
|
|
||||||
...params,
|
|
||||||
...(route.routeName === initialRouteName
|
|
||||||
? initialRouteParams
|
|
||||||
: null),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let the current child handle it
|
|
||||||
const activeChildLastState = state.routes[state.index];
|
|
||||||
const activeChildRouter = childRouters[order[state.index]];
|
|
||||||
if (activeChildRouter) {
|
|
||||||
const activeChildState = activeChildRouter.getStateForAction(
|
|
||||||
action,
|
|
||||||
activeChildLastState
|
|
||||||
);
|
|
||||||
if (!activeChildState && inputState) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (activeChildState && activeChildState !== activeChildLastState) {
|
|
||||||
const routes = [...state.routes];
|
|
||||||
routes[state.index] = activeChildState;
|
|
||||||
return getNextState(prevState, {
|
|
||||||
...state,
|
|
||||||
routes,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle tab changing. Do this after letting the current tab try to
|
|
||||||
// handle the action, to allow inner children to change first
|
|
||||||
const isBackEligible =
|
|
||||||
action.key == null || action.key === activeChildLastState.key;
|
|
||||||
if (action.type === NavigationActions.BACK) {
|
|
||||||
if (isBackEligible && shouldBackNavigateToInitialRoute) {
|
|
||||||
activeChildIndex = initialRouteIndex;
|
|
||||||
} else {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let didNavigate = false;
|
|
||||||
if (action.type === NavigationActions.NAVIGATE) {
|
|
||||||
didNavigate = !!order.find((childId, i) => {
|
|
||||||
if (childId === action.routeName) {
|
|
||||||
activeChildIndex = i;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
if (didNavigate) {
|
|
||||||
const childState = state.routes[activeChildIndex];
|
|
||||||
const childRouter = childRouters[action.routeName];
|
|
||||||
let newChildState;
|
|
||||||
|
|
||||||
if (action.action) {
|
|
||||||
newChildState = childRouter
|
|
||||||
? childRouter.getStateForAction(action.action, childState)
|
|
||||||
: null;
|
|
||||||
} else if (!action.action && action.params) {
|
|
||||||
newChildState = {
|
|
||||||
...childState,
|
|
||||||
params: {
|
|
||||||
...(childState.params || {}),
|
|
||||||
...action.params,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newChildState && newChildState !== childState) {
|
|
||||||
const routes = [...state.routes];
|
|
||||||
routes[activeChildIndex] = newChildState;
|
|
||||||
return getNextState(prevState, {
|
|
||||||
...state,
|
|
||||||
routes,
|
|
||||||
index: activeChildIndex,
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
!newChildState &&
|
|
||||||
state.index === activeChildIndex &&
|
|
||||||
prevState
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.type === NavigationActions.SET_PARAMS) {
|
|
||||||
const key = action.key;
|
|
||||||
const lastRoute = state.routes.find(route => route.key === key);
|
|
||||||
if (lastRoute) {
|
|
||||||
const params = {
|
|
||||||
...lastRoute.params,
|
|
||||||
...action.params,
|
|
||||||
};
|
|
||||||
const routes = [...state.routes];
|
|
||||||
routes[state.routes.indexOf(lastRoute)] = {
|
|
||||||
...lastRoute,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
return getNextState(prevState, {
|
|
||||||
...state,
|
|
||||||
routes,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeChildIndex !== state.index) {
|
|
||||||
return getNextState(prevState, {
|
|
||||||
...state,
|
|
||||||
index: activeChildIndex,
|
|
||||||
});
|
|
||||||
} else if (didNavigate && !inputState) {
|
|
||||||
return state;
|
|
||||||
} else if (didNavigate) {
|
|
||||||
return { ...state };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let other children handle it and switch to the first child that returns a new state
|
|
||||||
let index = state.index;
|
|
||||||
let routes = state.routes;
|
|
||||||
order.find((childId, i) => {
|
|
||||||
const childRouter = childRouters[childId];
|
|
||||||
if (i === index) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let childState = routes[i];
|
|
||||||
if (childRouter) {
|
|
||||||
childState = childRouter.getStateForAction(action, childState);
|
|
||||||
}
|
|
||||||
if (!childState) {
|
|
||||||
index = i;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (childState !== routes[i]) {
|
|
||||||
routes = [...routes];
|
|
||||||
routes[i] = childState;
|
|
||||||
index = i;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Nested routers can be updated after switching children with actions such as SET_PARAMS
|
|
||||||
// and COMPLETE_TRANSITION.
|
|
||||||
// NOTE: This may be problematic with custom routers because we whitelist the actions
|
|
||||||
// that can be handled by child routers without automatically changing index.
|
|
||||||
if (childrenUpdateWithoutSwitchingIndex(action.type)) {
|
|
||||||
index = state.index;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index !== state.index || routes !== state.routes) {
|
|
||||||
return getNextState(prevState, {
|
|
||||||
...state,
|
|
||||||
index,
|
|
||||||
routes,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
},
|
|
||||||
|
|
||||||
getComponentForState(state) {
|
|
||||||
const routeName = state.routes[state.index].routeName;
|
|
||||||
invariant(
|
|
||||||
routeName,
|
|
||||||
`There is no route defined for index ${state.index}. Check that
|
|
||||||
that you passed in a navigation state with a valid tab/screen index.`
|
|
||||||
);
|
|
||||||
const childRouter = childRouters[routeName];
|
|
||||||
if (childRouter) {
|
|
||||||
return childRouter.getComponentForState(state.routes[state.index]);
|
|
||||||
}
|
|
||||||
return getScreenForRouteName(routeConfigs, routeName);
|
|
||||||
},
|
|
||||||
|
|
||||||
getComponentForRouteName(routeName) {
|
|
||||||
return getScreenForRouteName(routeConfigs, routeName);
|
|
||||||
},
|
|
||||||
|
|
||||||
getPathAndParamsForState(state) {
|
|
||||||
const route = state.routes[state.index];
|
|
||||||
return getPathAndParamsForRoute(route);
|
|
||||||
},
|
|
||||||
|
|
||||||
getActionForPathAndParams(path, params) {
|
|
||||||
return getActionForPathAndParams(path, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
getScreenOptions: createConfigGetter(
|
|
||||||
routeConfigs,
|
|
||||||
config.defaultNavigationOptions
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,11 +0,0 @@
|
|||||||
import SwitchRouter from './SwitchRouter';
|
|
||||||
import withDefaultValue from '../utils/withDefaultValue';
|
|
||||||
|
|
||||||
export default (routeConfigs, config = {}) => {
|
|
||||||
config = { ...config };
|
|
||||||
config = withDefaultValue(config, 'resetOnBlur', false);
|
|
||||||
config = withDefaultValue(config, 'backBehavior', 'initialRoute');
|
|
||||||
|
|
||||||
const switchRouter = SwitchRouter(routeConfigs, config);
|
|
||||||
return switchRouter;
|
|
||||||
};
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../../.eslintrc",
|
|
||||||
"env": {
|
|
||||||
"jest": true
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,622 +0,0 @@
|
|||||||
/* eslint no-shadow:0, react/no-multi-comp:0, react/display-name:0 */
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import SwitchRouter from '../SwitchRouter';
|
|
||||||
import StackRouter from '../StackRouter';
|
|
||||||
import TabRouter from '../TabRouter';
|
|
||||||
import StackActions from '../StackActions';
|
|
||||||
import NavigationActions from '../../NavigationActions';
|
|
||||||
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
_TESTING_ONLY_normalize_keys();
|
|
||||||
});
|
|
||||||
|
|
||||||
const performRouterTest = createTestRouter => {
|
|
||||||
const ListScreen = () => <div />;
|
|
||||||
|
|
||||||
const ProfileNavigator = () => <div />;
|
|
||||||
ProfileNavigator.router = StackRouter({
|
|
||||||
list: {
|
|
||||||
path: 'list/:id',
|
|
||||||
screen: ListScreen,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const MainNavigator = () => <div />;
|
|
||||||
MainNavigator.router = StackRouter({
|
|
||||||
profile: {
|
|
||||||
path: 'p/:id',
|
|
||||||
screen: ProfileNavigator,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const LoginScreen = () => <div />;
|
|
||||||
|
|
||||||
const AuthNavigator = () => <div />;
|
|
||||||
AuthNavigator.router = StackRouter({
|
|
||||||
login: {
|
|
||||||
screen: LoginScreen,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const BarScreen = () => <div />;
|
|
||||||
|
|
||||||
class FooNavigator extends React.Component {
|
|
||||||
static router = StackRouter({
|
|
||||||
bar: {
|
|
||||||
path: 'b/:barThing',
|
|
||||||
screen: BarScreen,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
render() {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PersonScreen = () => <div />;
|
|
||||||
|
|
||||||
const testRouter = createTestRouter({
|
|
||||||
main: {
|
|
||||||
screen: MainNavigator,
|
|
||||||
},
|
|
||||||
baz: {
|
|
||||||
path: null,
|
|
||||||
screen: FooNavigator,
|
|
||||||
},
|
|
||||||
auth: {
|
|
||||||
screen: AuthNavigator,
|
|
||||||
},
|
|
||||||
person: {
|
|
||||||
path: 'people/:id',
|
|
||||||
screen: PersonScreen,
|
|
||||||
},
|
|
||||||
foo: {
|
|
||||||
path: 'fo/:fooThing',
|
|
||||||
screen: FooNavigator,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles empty URIs with empty action', () => {
|
|
||||||
const router = createTestRouter(
|
|
||||||
{
|
|
||||||
Foo: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ initialRouteName: 'Bar', initialRouteParams: { foo: 42 } }
|
|
||||||
);
|
|
||||||
const action = router.getActionForPathAndParams('');
|
|
||||||
expect(action).toEqual(null);
|
|
||||||
const state = router.getStateForAction(action || NavigationActions.init());
|
|
||||||
expect(state.routes[state.index]).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
routeName: 'Bar',
|
|
||||||
params: { foo: 42 },
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles paths with several params', () => {
|
|
||||||
const router = createTestRouter({
|
|
||||||
Person: {
|
|
||||||
path: 'people/:person',
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
Task: {
|
|
||||||
path: 'people/:person/tasks/:task',
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
ThingA: {
|
|
||||||
path: 'things/:good',
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
Thing: {
|
|
||||||
path: 'things/:good/:thing',
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const action = router.getActionForPathAndParams(
|
|
||||||
'people/brent/tasks/everything'
|
|
||||||
);
|
|
||||||
expect(action).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Task',
|
|
||||||
params: { person: 'brent', task: 'everything' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const action1 = router.getActionForPathAndParams('people/lucy');
|
|
||||||
expect(action1).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Person',
|
|
||||||
params: { person: 'lucy' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const action2 = router.getActionForPathAndParams('things/foo/bar');
|
|
||||||
expect(action2).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Thing',
|
|
||||||
params: { good: 'foo', thing: 'bar' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const action3 = router.getActionForPathAndParams('things/foo');
|
|
||||||
expect(action3).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'ThingA',
|
|
||||||
params: { good: 'foo' },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles empty path configuration', () => {
|
|
||||||
const router = createTestRouter({
|
|
||||||
Foo: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: () => <div />,
|
|
||||||
path: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const action = router.getActionForPathAndParams('');
|
|
||||||
expect(action).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
params: {},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles wildcard path configuration', () => {
|
|
||||||
const router = createTestRouter({
|
|
||||||
Foo: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: () => <div />,
|
|
||||||
path: ':something',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const action = router.getActionForPathAndParams('');
|
|
||||||
expect(action).toEqual(null);
|
|
||||||
|
|
||||||
const action1 = router.getActionForPathAndParams('Foo');
|
|
||||||
expect(action1).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Foo',
|
|
||||||
params: {},
|
|
||||||
});
|
|
||||||
const action2 = router.getActionForPathAndParams('asdf');
|
|
||||||
expect(action2).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
params: { something: 'asdf' },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Null path behavior', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const router = createTestRouter({
|
|
||||||
Bar: {
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
Foo: {
|
|
||||||
path: null,
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
Baz: {
|
|
||||||
path: '',
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const action0 = router.getActionForPathAndParams('test/random', {});
|
|
||||||
expect(action0).toBe(null);
|
|
||||||
|
|
||||||
const action1 = router.getActionForPathAndParams('', {});
|
|
||||||
expect(action1.routeName).toBe('Baz');
|
|
||||||
const state1 = router.getStateForAction(action1);
|
|
||||||
expect(state1.routes[state1.index].routeName).toBe('Baz');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Multiple null path sub routers path behavior', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
ScreenB.router = createTestRouter({
|
|
||||||
Foo: ScreenA,
|
|
||||||
});
|
|
||||||
const ScreenC = () => <div />;
|
|
||||||
ScreenC.router = createTestRouter({
|
|
||||||
Bar: {
|
|
||||||
path: 'bar/:id',
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
Empty: {
|
|
||||||
path: '',
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const router = createTestRouter({
|
|
||||||
A: {
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
B: {
|
|
||||||
path: null,
|
|
||||||
screen: ScreenB,
|
|
||||||
},
|
|
||||||
C: {
|
|
||||||
path: null,
|
|
||||||
screen: ScreenC,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const action0 = router.getActionForPathAndParams('Foo', {});
|
|
||||||
expect(action0.routeName).toBe('B');
|
|
||||||
expect(action0.action.routeName).toBe('Foo');
|
|
||||||
|
|
||||||
const action1 = router.getActionForPathAndParams('', {});
|
|
||||||
expect(action1.routeName).toBe('C');
|
|
||||||
expect(action1.action.routeName).toBe('Empty');
|
|
||||||
|
|
||||||
const action2 = router.getActionForPathAndParams('A', {});
|
|
||||||
expect(action2.routeName).toBe('A');
|
|
||||||
|
|
||||||
const action3 = router.getActionForPathAndParams('bar/asdf', {});
|
|
||||||
expect(action3.routeName).toBe('C');
|
|
||||||
expect(action3.action.routeName).toBe('Bar');
|
|
||||||
expect(action3.action.params.id).toBe('asdf');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Null and empty string path sub routers behavior', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
ScreenB.router = createTestRouter({
|
|
||||||
Foo: ScreenA,
|
|
||||||
Baz: {
|
|
||||||
screen: ScreenA,
|
|
||||||
path: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const ScreenC = () => <div />;
|
|
||||||
ScreenC.router = createTestRouter({
|
|
||||||
Boo: ScreenA,
|
|
||||||
Bar: ScreenA,
|
|
||||||
Baz: {
|
|
||||||
screen: ScreenA,
|
|
||||||
path: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const router = createTestRouter({
|
|
||||||
B: {
|
|
||||||
path: null,
|
|
||||||
screen: ScreenB,
|
|
||||||
},
|
|
||||||
C: {
|
|
||||||
path: '',
|
|
||||||
screen: ScreenC,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const action0 = router.getActionForPathAndParams('', {});
|
|
||||||
expect(action0.routeName).toBe('C');
|
|
||||||
expect(action0.action.routeName).toBe('Baz');
|
|
||||||
|
|
||||||
const action1 = router.getActionForPathAndParams('Foo', {});
|
|
||||||
expect(action1.routeName).toBe('B');
|
|
||||||
expect(action1.action.routeName).toBe('Foo');
|
|
||||||
|
|
||||||
const action2 = router.getActionForPathAndParams('Bar', {});
|
|
||||||
expect(action2.routeName).toBe('C');
|
|
||||||
expect(action2.action.routeName).toBe('Bar');
|
|
||||||
|
|
||||||
const action3 = router.getActionForPathAndParams('unknown', {});
|
|
||||||
expect(action3).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Empty path acts as wildcard for nested router', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const Foo = () => <div />;
|
|
||||||
const ScreenC = () => <div />;
|
|
||||||
ScreenC.router = createTestRouter({
|
|
||||||
Boo: ScreenA,
|
|
||||||
Bar: ScreenA,
|
|
||||||
});
|
|
||||||
Foo.router = createTestRouter({
|
|
||||||
Quo: ScreenA,
|
|
||||||
Qux: {
|
|
||||||
screen: ScreenC,
|
|
||||||
path: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const router = createTestRouter({
|
|
||||||
Bar: {
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
Foo,
|
|
||||||
});
|
|
||||||
const action0 = router.getActionForPathAndParams('Foo/Bar', {});
|
|
||||||
expect(action0.routeName).toBe('Foo');
|
|
||||||
expect(action0.action.routeName).toBe('Qux');
|
|
||||||
expect(action0.action.action.routeName).toBe('Bar');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Gets deep path with pure wildcard match', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
const ScreenC = () => <div />;
|
|
||||||
ScreenA.router = createTestRouter({
|
|
||||||
Boo: { path: 'boo', screen: ScreenC },
|
|
||||||
Baz: { path: 'baz/:bazId', screen: ScreenB },
|
|
||||||
});
|
|
||||||
ScreenC.router = createTestRouter({
|
|
||||||
Boo2: { path: '', screen: ScreenB },
|
|
||||||
});
|
|
||||||
const router = createTestRouter({
|
|
||||||
Foo: {
|
|
||||||
path: null,
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: ScreenB,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
params: {
|
|
||||||
id: '123',
|
|
||||||
},
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Boo',
|
|
||||||
routeName: 'Boo',
|
|
||||||
routes: [{ key: 'Boo2', routeName: 'Boo2' }],
|
|
||||||
},
|
|
||||||
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const { path, params } = router.getPathAndParamsForState(state);
|
|
||||||
expect(path).toEqual('baz/321');
|
|
||||||
expect(params.id).toEqual('123');
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
params: {
|
|
||||||
id: '123',
|
|
||||||
},
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Boo',
|
|
||||||
routeName: 'Boo',
|
|
||||||
routes: [{ key: 'Boo2', routeName: 'Boo2' }],
|
|
||||||
},
|
|
||||||
{ key: 'Baz', routeName: 'Baz', params: { bazId: '321' } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const { path, params } = router.getPathAndParamsForState(state);
|
|
||||||
expect(path).toEqual('boo');
|
|
||||||
expect(params).toEqual({ id: '123' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('URI encoded string get passed to deep link', () => {
|
|
||||||
const uri = 'people/2018%2F02%2F07';
|
|
||||||
const action = testRouter.getActionForPathAndParams(uri);
|
|
||||||
expect(action).toEqual({
|
|
||||||
routeName: 'person',
|
|
||||||
params: {
|
|
||||||
id: '2018/02/07',
|
|
||||||
},
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
});
|
|
||||||
|
|
||||||
const malformedUri = 'people/%E0%A4%A';
|
|
||||||
const action2 = testRouter.getActionForPathAndParams(malformedUri);
|
|
||||||
expect(action2).toEqual({
|
|
||||||
routeName: 'person',
|
|
||||||
params: {
|
|
||||||
id: '%E0%A4%A',
|
|
||||||
},
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('URI encoded path param gets parsed and correctly printed', () => {
|
|
||||||
const action = testRouter.getActionForPathAndParams('people/Henry%20L');
|
|
||||||
expect(action).toEqual({
|
|
||||||
routeName: 'person',
|
|
||||||
params: {
|
|
||||||
id: 'Henry L',
|
|
||||||
},
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
});
|
|
||||||
const s = testRouter.getStateForAction(action);
|
|
||||||
const out = testRouter.getPathAndParamsForState(s);
|
|
||||||
expect(out.path).toEqual('people/Henry%20L');
|
|
||||||
expect(out.params).toEqual({});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Querystring params get passed to nested deep link', () => {
|
|
||||||
const action = testRouter.getActionForPathAndParams(
|
|
||||||
'main/p/4/list/10259959195',
|
|
||||||
{ code: 'test', foo: 'bar' }
|
|
||||||
);
|
|
||||||
expect(action).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'main',
|
|
||||||
params: {
|
|
||||||
code: 'test',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'profile',
|
|
||||||
params: {
|
|
||||||
id: '4',
|
|
||||||
code: 'test',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'list',
|
|
||||||
params: {
|
|
||||||
id: '10259959195',
|
|
||||||
code: 'test',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const action2 = testRouter.getActionForPathAndParams(
|
|
||||||
'main/p/4/list/10259959195',
|
|
||||||
{ code: '', foo: 'bar' }
|
|
||||||
);
|
|
||||||
expect(action2).toEqual({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'main',
|
|
||||||
params: {
|
|
||||||
code: '',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'profile',
|
|
||||||
params: {
|
|
||||||
id: '4',
|
|
||||||
code: '',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'list',
|
|
||||||
params: {
|
|
||||||
id: '10259959195',
|
|
||||||
code: '',
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('paths option on router overrides path from route config', () => {
|
|
||||||
const router = createTestRouter(
|
|
||||||
{
|
|
||||||
main: {
|
|
||||||
screen: MainNavigator,
|
|
||||||
},
|
|
||||||
baz: {
|
|
||||||
path: null,
|
|
||||||
screen: FooNavigator,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ paths: { baz: 'overridden' } }
|
|
||||||
);
|
|
||||||
const action = router.getActionForPathAndParams('overridden', {});
|
|
||||||
expect(action.type).toEqual(NavigationActions.NAVIGATE);
|
|
||||||
expect(action.routeName).toEqual('baz');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('paths option set as null on router overrides path from route config', () => {
|
|
||||||
const router = createTestRouter(
|
|
||||||
{
|
|
||||||
main: {
|
|
||||||
screen: MainNavigator,
|
|
||||||
},
|
|
||||||
baz: {
|
|
||||||
path: 'bazPath',
|
|
||||||
screen: FooNavigator,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ paths: { baz: null } }
|
|
||||||
);
|
|
||||||
const action = router.getActionForPathAndParams('b/noBaz', {});
|
|
||||||
expect(action.type).toEqual(NavigationActions.NAVIGATE);
|
|
||||||
expect(action.routeName).toEqual('baz');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Path handling for stack router', () => {
|
|
||||||
performRouterTest(StackRouter);
|
|
||||||
});
|
|
||||||
describe('Path handling for switch router', () => {
|
|
||||||
performRouterTest(SwitchRouter);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles nested switch routers', () => {
|
|
||||||
const AScreen = () => <div />;
|
|
||||||
const DocsNavigator = () => <div />;
|
|
||||||
DocsNavigator.router = SwitchRouter({
|
|
||||||
A: AScreen,
|
|
||||||
B: AScreen,
|
|
||||||
C: AScreen,
|
|
||||||
});
|
|
||||||
DocsNavigator.path = 'docs';
|
|
||||||
const router = SwitchRouter({
|
|
||||||
Docs: DocsNavigator,
|
|
||||||
D: AScreen,
|
|
||||||
});
|
|
||||||
const action = router.getActionForPathAndParams('docs/B', {});
|
|
||||||
|
|
||||||
expect(action.type).toEqual(NavigationActions.NAVIGATE);
|
|
||||||
expect(action.routeName).toEqual('Docs');
|
|
||||||
expect(action.action.type).toEqual(NavigationActions.NAVIGATE);
|
|
||||||
expect(action.action.routeName).toEqual('B');
|
|
||||||
});
|
|
||||||
|
|
||||||
const performRouteNameAsPathDisabledTest = createTestRouter => {
|
|
||||||
const BScreen = () => <div />;
|
|
||||||
const NestedNavigator = () => <div />;
|
|
||||||
NestedNavigator.router = createTestRouter({
|
|
||||||
B: {
|
|
||||||
screen: BScreen,
|
|
||||||
path: 'baz',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const router = createTestRouter(
|
|
||||||
{
|
|
||||||
A: NestedNavigator,
|
|
||||||
},
|
|
||||||
{ disableRouteNamePaths: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
test('disableRouteNamePaths option on router prevent the default path to be the routeName', () => {
|
|
||||||
const action = router.getActionForPathAndParams('baz', {});
|
|
||||||
|
|
||||||
expect(action.routeName).toBe('A');
|
|
||||||
expect(action.action.routeName).toBe('B');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Stack router handles disableRouteNamePaths', () => {
|
|
||||||
performRouteNameAsPathDisabledTest(StackRouter);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Switch router handles disableRouteNamePaths', () => {
|
|
||||||
performRouteNameAsPathDisabledTest(SwitchRouter);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Tab router handles disableRouteNamePaths', () => {
|
|
||||||
performRouteNameAsPathDisabledTest(TabRouter);
|
|
||||||
});
|
|
@ -1,470 +0,0 @@
|
|||||||
/* eslint react/no-multi-comp:0, react/display-name:0 */
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import StackRouter from '../StackRouter';
|
|
||||||
import TabRouter from '../TabRouter';
|
|
||||||
import SwitchRouter from '../SwitchRouter';
|
|
||||||
|
|
||||||
import NavigationActions from '../../NavigationActions';
|
|
||||||
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
_TESTING_ONLY_normalize_keys();
|
|
||||||
});
|
|
||||||
|
|
||||||
const ROUTERS = {
|
|
||||||
TabRouter,
|
|
||||||
StackRouter,
|
|
||||||
SwitchRouter,
|
|
||||||
};
|
|
||||||
|
|
||||||
const dummyEventSubscriber = (name, handler) => ({
|
|
||||||
remove: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(ROUTERS).forEach(routerName => {
|
|
||||||
const Router = ROUTERS[routerName];
|
|
||||||
|
|
||||||
describe(`General router features - ${routerName}`, () => {
|
|
||||||
test(`title is configurable using navigationOptions and getScreenOptions - ${routerName}`, () => {
|
|
||||||
class FooView extends React.Component {
|
|
||||||
render() {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class BarView extends React.Component {
|
|
||||||
render() {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
static navigationOptions = { title: 'BarTitle' };
|
|
||||||
}
|
|
||||||
class BazView extends React.Component {
|
|
||||||
render() {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
static navigationOptions = ({ navigation }) => ({
|
|
||||||
title: `Baz-${navigation.state.params.id}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const router = Router({
|
|
||||||
Foo: { screen: FooView },
|
|
||||||
Bar: { screen: BarView },
|
|
||||||
Baz: { screen: BazView },
|
|
||||||
});
|
|
||||||
const routes = [
|
|
||||||
{ key: 'A', routeName: 'Foo' },
|
|
||||||
{ key: 'B', routeName: 'Bar' },
|
|
||||||
{ key: 'A', routeName: 'Baz', params: { id: '123' } },
|
|
||||||
];
|
|
||||||
expect(
|
|
||||||
router.getScreenOptions(
|
|
||||||
{
|
|
||||||
state: routes[0],
|
|
||||||
dispatch: () => false,
|
|
||||||
addListener: dummyEventSubscriber,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
).title
|
|
||||||
).toEqual(undefined);
|
|
||||||
expect(
|
|
||||||
router.getScreenOptions(
|
|
||||||
{
|
|
||||||
state: routes[1],
|
|
||||||
dispatch: () => false,
|
|
||||||
addListener: dummyEventSubscriber,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
).title
|
|
||||||
).toEqual('BarTitle');
|
|
||||||
expect(
|
|
||||||
router.getScreenOptions(
|
|
||||||
{
|
|
||||||
state: routes[2],
|
|
||||||
dispatch: () => false,
|
|
||||||
addListener: dummyEventSubscriber,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
).title
|
|
||||||
).toEqual('Baz-123');
|
|
||||||
});
|
|
||||||
|
|
||||||
test(`set params works in ${routerName}`, () => {
|
|
||||||
class FooView extends React.Component {
|
|
||||||
render() {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const router = Router({
|
|
||||||
Foo: { screen: FooView },
|
|
||||||
Bar: { screen: FooView },
|
|
||||||
});
|
|
||||||
|
|
||||||
const initState = router.getStateForAction(NavigationActions.init());
|
|
||||||
const initRoute = initState.routes[initState.index];
|
|
||||||
expect(initRoute.params).toEqual(undefined);
|
|
||||||
|
|
||||||
const state0 = router.getStateForAction(
|
|
||||||
NavigationActions.setParams({
|
|
||||||
params: { foo: 42 },
|
|
||||||
key: initRoute.key,
|
|
||||||
}),
|
|
||||||
initState
|
|
||||||
);
|
|
||||||
expect(state0.routes[state0.index].params.foo).toEqual(42);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Nested navigate behavior test', () => {
|
|
||||||
const Leaf = () => <div />;
|
|
||||||
|
|
||||||
const First = () => <div />;
|
|
||||||
First.router = StackRouter({
|
|
||||||
First1: Leaf,
|
|
||||||
First2: Leaf,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Second = () => <div />;
|
|
||||||
Second.router = StackRouter({
|
|
||||||
Second1: Leaf,
|
|
||||||
Second2: Leaf,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Main = () => <div />;
|
|
||||||
Main.router = StackRouter({
|
|
||||||
First,
|
|
||||||
Second,
|
|
||||||
});
|
|
||||||
const TestRouter = SwitchRouter({
|
|
||||||
Login: Leaf,
|
|
||||||
Main,
|
|
||||||
});
|
|
||||||
|
|
||||||
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
|
|
||||||
const state2 = TestRouter.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'First' },
|
|
||||||
state1
|
|
||||||
);
|
|
||||||
expect(state2.index).toEqual(1);
|
|
||||||
expect(state2.routes[1].index).toEqual(0);
|
|
||||||
expect(state2.routes[1].routes[0].index).toEqual(0);
|
|
||||||
|
|
||||||
const state3 = TestRouter.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Second2' },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3.index).toEqual(1);
|
|
||||||
expect(state3.routes[1].index).toEqual(1); // second
|
|
||||||
expect(state3.routes[1].routes[1].index).toEqual(1); //second.second2
|
|
||||||
|
|
||||||
const state4 = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'First',
|
|
||||||
action: { type: NavigationActions.NAVIGATE, routeName: 'First2' },
|
|
||||||
},
|
|
||||||
state3,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
expect(state4.index).toEqual(1); // main
|
|
||||||
expect(state4.routes[1].index).toEqual(0); // first
|
|
||||||
expect(state4.routes[1].routes[0].index).toEqual(1); // first2
|
|
||||||
|
|
||||||
const state5 = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'First',
|
|
||||||
action: { type: NavigationActions.NAVIGATE, routeName: 'First1' },
|
|
||||||
},
|
|
||||||
state3 // second.second2 is active on state3
|
|
||||||
);
|
|
||||||
expect(state5.index).toEqual(1); // main
|
|
||||||
expect(state5.routes[1].index).toEqual(0); // first
|
|
||||||
expect(state5.routes[1].routes[0].index).toEqual(0); // first.first1
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles no-op actions with tabs within stack router', () => {
|
|
||||||
const BarView = () => <div />;
|
|
||||||
const FooTabNavigator = () => <div />;
|
|
||||||
FooTabNavigator.router = TabRouter({
|
|
||||||
Zap: { screen: BarView },
|
|
||||||
Zoo: { screen: BarView },
|
|
||||||
});
|
|
||||||
const TestRouter = StackRouter({
|
|
||||||
Foo: {
|
|
||||||
screen: FooTabNavigator,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: BarView,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const state2 = TestRouter.getStateForAction({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Qux',
|
|
||||||
});
|
|
||||||
expect(state1.routes[0].key).toEqual('id-0');
|
|
||||||
expect(state2.routes[0].key).toEqual('id-1');
|
|
||||||
state1.routes[0].key = state2.routes[0].key;
|
|
||||||
expect(state1).toEqual(state2);
|
|
||||||
const state3 = TestRouter.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Zap' },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state2).toEqual(state3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles deep action', () => {
|
|
||||||
const BarView = () => <div />;
|
|
||||||
const FooTabNavigator = () => <div />;
|
|
||||||
FooTabNavigator.router = TabRouter({
|
|
||||||
Zap: { screen: BarView },
|
|
||||||
Zoo: { screen: BarView },
|
|
||||||
});
|
|
||||||
const TestRouter = StackRouter({
|
|
||||||
Bar: { screen: BarView },
|
|
||||||
Foo: { screen: FooTabNavigator },
|
|
||||||
});
|
|
||||||
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const expectedState = {
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
key: 'StackRouterRoot',
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
key: 'id-0',
|
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
expect(state1).toEqual(expectedState);
|
|
||||||
const state2 = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Foo',
|
|
||||||
immediate: true,
|
|
||||||
action: { type: NavigationActions.NAVIGATE, routeName: 'Zoo' },
|
|
||||||
},
|
|
||||||
state1
|
|
||||||
);
|
|
||||||
expect(state2 && state2.index).toEqual(1);
|
|
||||||
expect(state2 && state2.routes[1].index).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles the navigate action with params', () => {
|
|
||||||
const FooTabNavigator = () => <div />;
|
|
||||||
FooTabNavigator.router = TabRouter({
|
|
||||||
Baz: { screen: () => <div /> },
|
|
||||||
Boo: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
|
|
||||||
const TestRouter = StackRouter({
|
|
||||||
Foo: { screen: () => <div /> },
|
|
||||||
Bar: { screen: FooTabNavigator },
|
|
||||||
});
|
|
||||||
const state = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const state2 = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
immediate: true,
|
|
||||||
routeName: 'Bar',
|
|
||||||
params: { foo: '42' },
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2 && state2.routes[1].params).toEqual({ foo: '42' });
|
|
||||||
expect(state2 && state2.routes[1].routes).toEqual([
|
|
||||||
{
|
|
||||||
key: 'Baz',
|
|
||||||
routeName: 'Baz',
|
|
||||||
params: { foo: '42' },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'Boo',
|
|
||||||
routeName: 'Boo',
|
|
||||||
params: { foo: '42' },
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles the setParams action', () => {
|
|
||||||
const FooTabNavigator = () => <div />;
|
|
||||||
FooTabNavigator.router = TabRouter({
|
|
||||||
Baz: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
const TestRouter = StackRouter({
|
|
||||||
Foo: { screen: FooTabNavigator },
|
|
||||||
Bar: { screen: () => <div /> },
|
|
||||||
});
|
|
||||||
const state = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const state2 = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.SET_PARAMS,
|
|
||||||
params: { name: 'foobar' },
|
|
||||||
key: 'Baz',
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2 && state2.index).toEqual(0);
|
|
||||||
expect(state2 && state2.routes[0].routes).toEqual([
|
|
||||||
{
|
|
||||||
key: 'Baz',
|
|
||||||
routeName: 'Baz',
|
|
||||||
params: { name: 'foobar' },
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Supports lazily-evaluated getScreen', () => {
|
|
||||||
const BarView = () => <div />;
|
|
||||||
const FooTabNavigator = () => <div />;
|
|
||||||
FooTabNavigator.router = TabRouter({
|
|
||||||
Zap: { screen: BarView },
|
|
||||||
Zoo: { screen: BarView },
|
|
||||||
});
|
|
||||||
const TestRouter = StackRouter({
|
|
||||||
Foo: {
|
|
||||||
screen: FooTabNavigator,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
getScreen: () => BarView,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const state2 = TestRouter.getStateForAction({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
immediate: true,
|
|
||||||
routeName: 'Qux',
|
|
||||||
});
|
|
||||||
expect(state1.routes[0].key).toEqual('id-0');
|
|
||||||
expect(state2.routes[0].key).toEqual('id-1');
|
|
||||||
state1.routes[0].key = state2.routes[0].key;
|
|
||||||
expect(state1).toEqual(state2);
|
|
||||||
const state3 = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
immediate: true,
|
|
||||||
routeName: 'Zap',
|
|
||||||
},
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state2).toEqual(state3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Does not switch tab index when TabRouter child handles COMPLETE_NAVIGATION or SET_PARAMS', () => {
|
|
||||||
const FooStackNavigator = () => <div />;
|
|
||||||
const BarView = () => <div />;
|
|
||||||
FooStackNavigator.router = StackRouter({
|
|
||||||
Foo: {
|
|
||||||
screen: BarView,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: BarView,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const TestRouter = TabRouter({
|
|
||||||
Zap: { screen: FooStackNavigator },
|
|
||||||
Zoo: { screen: FooStackNavigator },
|
|
||||||
});
|
|
||||||
|
|
||||||
const state1 = TestRouter.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
|
|
||||||
// Navigate to the second screen in the first tab
|
|
||||||
const state2 = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
|
||||||
state1
|
|
||||||
);
|
|
||||||
|
|
||||||
// Switch tabs
|
|
||||||
const state3 = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Zoo',
|
|
||||||
},
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
|
|
||||||
const stateAfterCompleteTransition = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.COMPLETE_TRANSITION,
|
|
||||||
key: state2.routes[0].key,
|
|
||||||
},
|
|
||||||
state3
|
|
||||||
);
|
|
||||||
const stateAfterSetParams = TestRouter.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.SET_PARAMS,
|
|
||||||
key: state1.routes[0].routes[0].key,
|
|
||||||
params: { key: 'value' },
|
|
||||||
},
|
|
||||||
state3
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(stateAfterCompleteTransition.index).toEqual(1);
|
|
||||||
expect(stateAfterSetParams.index).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Inner actions are only unpacked if the current tab matches', () => {
|
|
||||||
const PlainScreen = () => <div />;
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
ScreenB.router = StackRouter({
|
|
||||||
Baz: { screen: PlainScreen },
|
|
||||||
Zoo: { screen: PlainScreen },
|
|
||||||
});
|
|
||||||
ScreenA.router = StackRouter({
|
|
||||||
Bar: { screen: PlainScreen },
|
|
||||||
Boo: { screen: ScreenB },
|
|
||||||
});
|
|
||||||
const TestRouter = TabRouter({
|
|
||||||
Foo: { screen: ScreenA },
|
|
||||||
});
|
|
||||||
const screenApreState = {
|
|
||||||
index: 0,
|
|
||||||
key: 'Init',
|
|
||||||
isTransitioning: false,
|
|
||||||
routeName: 'Foo',
|
|
||||||
routes: [{ key: 'Init', routeName: 'Bar' }],
|
|
||||||
};
|
|
||||||
const preState = {
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [screenApreState],
|
|
||||||
};
|
|
||||||
|
|
||||||
const comparable = state => {
|
|
||||||
let result = {};
|
|
||||||
if (typeof state.routeName === 'string') {
|
|
||||||
result = { ...result, routeName: state.routeName };
|
|
||||||
}
|
|
||||||
if (state.routes instanceof Array) {
|
|
||||||
result = {
|
|
||||||
...result,
|
|
||||||
routes: state.routes.map(comparable),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const action = NavigationActions.navigate({
|
|
||||||
routeName: 'Boo',
|
|
||||||
action: NavigationActions.navigate({ routeName: 'Zoo' }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedState = ScreenA.router.getStateForAction(
|
|
||||||
action,
|
|
||||||
screenApreState
|
|
||||||
);
|
|
||||||
const state = TestRouter.getStateForAction(action, preState);
|
|
||||||
const innerState = state ? state.routes[0] : state;
|
|
||||||
|
|
||||||
expect(expectedState && comparable(expectedState)).toEqual(
|
|
||||||
innerState && comparable(innerState)
|
|
||||||
);
|
|
||||||
});
|
|
@ -1,202 +0,0 @@
|
|||||||
/* eslint react/display-name:0 */
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import SwitchRouter from '../SwitchRouter';
|
|
||||||
import StackRouter from '../StackRouter';
|
|
||||||
import NavigationActions from '../../NavigationActions';
|
|
||||||
|
|
||||||
describe('SwitchRouter', () => {
|
|
||||||
test('resets the route when unfocusing a tab by default', () => {
|
|
||||||
const router = getExampleRouter();
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'A2' },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.routes[0].index).toEqual(1);
|
|
||||||
expect(state2.routes[0].routes.length).toEqual(2);
|
|
||||||
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(state3.routes[0].index).toEqual(0);
|
|
||||||
expect(state3.routes[0].routes.length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('does not reset the route on unfocus if resetOnBlur is false', () => {
|
|
||||||
const router = getExampleRouter({ resetOnBlur: false });
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'A2' },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.routes[0].index).toEqual(1);
|
|
||||||
expect(state2.routes[0].routes.length).toEqual(2);
|
|
||||||
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(state3.routes[0].index).toEqual(1);
|
|
||||||
expect(state3.routes[0].routes.length).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('ignores back by default', () => {
|
|
||||||
const router = getExampleRouter();
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.index).toEqual(1);
|
|
||||||
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.BACK },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(state3.index).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('handles back if given a backBehavior', () => {
|
|
||||||
const router = getExampleRouter({ backBehavior: 'initialRoute' });
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.index).toEqual(1);
|
|
||||||
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.BACK },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(state3.index).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('order of handling navigate action is correct for nested switchrouters', () => {
|
|
||||||
// router = switch({ Nested: switch({ Foo, Bar }), Other: switch({ Foo }), Bar })
|
|
||||||
// if we are focused on Other and navigate to Bar, what should happen?
|
|
||||||
|
|
||||||
const Screen = () => <div />;
|
|
||||||
const NestedSwitch = () => <div />;
|
|
||||||
const OtherNestedSwitch = () => <div />;
|
|
||||||
|
|
||||||
let nestedRouter = SwitchRouter({ Foo: Screen, Bar: Screen });
|
|
||||||
let otherNestedRouter = SwitchRouter({ Foo: Screen });
|
|
||||||
NestedSwitch.router = nestedRouter;
|
|
||||||
OtherNestedSwitch.router = otherNestedRouter;
|
|
||||||
|
|
||||||
let router = SwitchRouter(
|
|
||||||
{
|
|
||||||
NestedSwitch,
|
|
||||||
OtherNestedSwitch,
|
|
||||||
Bar: Screen,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'OtherNestedSwitch',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
expect(state.routes[state.index].routeName).toEqual('OtherNestedSwitch');
|
|
||||||
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.routes[state2.index].routeName).toEqual('Bar');
|
|
||||||
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'NestedSwitch',
|
|
||||||
},
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
const state4 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
},
|
|
||||||
state3
|
|
||||||
);
|
|
||||||
let activeState4 = state4.routes[state4.index];
|
|
||||||
expect(activeState4.routeName).toEqual('NestedSwitch');
|
|
||||||
expect(activeState4.routes[activeState4.index].routeName).toEqual('Bar');
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://github.com/react-navigation/react-navigation.github.io/issues/117#issuecomment-385597628
|
|
||||||
test('order of handling navigate action is correct for nested stackrouters', () => {
|
|
||||||
const Screen = () => <div />;
|
|
||||||
const MainStack = () => <div />;
|
|
||||||
const LoginStack = () => <div />;
|
|
||||||
MainStack.router = StackRouter({ Home: Screen, Profile: Screen });
|
|
||||||
LoginStack.router = StackRouter({ Form: Screen, ForgotPassword: Screen });
|
|
||||||
|
|
||||||
let router = SwitchRouter(
|
|
||||||
{
|
|
||||||
Home: Screen,
|
|
||||||
Login: LoginStack,
|
|
||||||
Main: MainStack,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'Login',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
expect(state.routes[state.index].routeName).toEqual('Login');
|
|
||||||
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Home',
|
|
||||||
},
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2.routes[state2.index].routeName).toEqual('Home');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const getExampleRouter = (config = {}) => {
|
|
||||||
const PlainScreen = () => <div />;
|
|
||||||
const StackA = () => <div />;
|
|
||||||
const StackB = () => <div />;
|
|
||||||
|
|
||||||
StackA.router = StackRouter({
|
|
||||||
A1: PlainScreen,
|
|
||||||
A2: PlainScreen,
|
|
||||||
});
|
|
||||||
|
|
||||||
StackB.router = StackRouter({
|
|
||||||
B1: PlainScreen,
|
|
||||||
B2: PlainScreen,
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = SwitchRouter(
|
|
||||||
{
|
|
||||||
A: {
|
|
||||||
screen: StackA,
|
|
||||||
path: '',
|
|
||||||
},
|
|
||||||
B: {
|
|
||||||
screen: StackB,
|
|
||||||
path: 'great/path',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'A',
|
|
||||||
...config,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return router;
|
|
||||||
};
|
|
@ -1,819 +0,0 @@
|
|||||||
/* eslint react/display-name:0 */
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import TabRouter from '../TabRouter';
|
|
||||||
|
|
||||||
import NavigationActions from '../../NavigationActions';
|
|
||||||
|
|
||||||
const INIT_ACTION = { type: NavigationActions.INIT };
|
|
||||||
|
|
||||||
const BareLeafRouteConfig = {
|
|
||||||
screen: () => <div />,
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('TabRouter', () => {
|
|
||||||
test('Handles basic tab logic', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
const router = TabRouter({
|
|
||||||
Foo: { screen: ScreenA },
|
|
||||||
Bar: { screen: ScreenB },
|
|
||||||
});
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const expectedState = {
|
|
||||||
index: 0,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(state).toEqual(expectedState);
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
const expectedState2 = {
|
|
||||||
index: 1,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(state2).toEqual(expectedState2);
|
|
||||||
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
|
|
||||||
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3).toEqual(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles getScreen', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
const router = TabRouter({
|
|
||||||
Foo: { getScreen: () => ScreenA },
|
|
||||||
Bar: { getScreen: () => ScreenB },
|
|
||||||
});
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const expectedState = {
|
|
||||||
index: 0,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(state).toEqual(expectedState);
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
const expectedState2 = {
|
|
||||||
index: 1,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
isTransitioning: false,
|
|
||||||
};
|
|
||||||
expect(state2).toEqual(expectedState2);
|
|
||||||
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
|
|
||||||
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3).toEqual(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Can set the initial tab', () => {
|
|
||||||
const router = TabRouter(
|
|
||||||
{ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig },
|
|
||||||
{ initialRouteName: 'Bar' }
|
|
||||||
);
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
expect(state).toEqual({
|
|
||||||
index: 1,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
isTransitioning: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Can set the initial params', () => {
|
|
||||||
const router = TabRouter(
|
|
||||||
{ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig },
|
|
||||||
{ initialRouteName: 'Bar', initialRouteParams: { name: 'Qux' } }
|
|
||||||
);
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
expect(state).toEqual({
|
|
||||||
index: 1,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
|
||||||
{ key: 'Bar', routeName: 'Bar', params: { name: 'Qux' } },
|
|
||||||
],
|
|
||||||
isTransitioning: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles the SetParams action', () => {
|
|
||||||
const router = TabRouter({
|
|
||||||
Foo: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const state2 = router.getStateForAction({
|
|
||||||
type: NavigationActions.SET_PARAMS,
|
|
||||||
params: { name: 'Qux' },
|
|
||||||
key: 'Foo',
|
|
||||||
});
|
|
||||||
expect(state2 && state2.routes[0].params).toEqual({ name: 'Qux' });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles the SetParams action for inactive routes', () => {
|
|
||||||
const router = TabRouter(
|
|
||||||
{
|
|
||||||
Foo: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: () => <div />,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialRouteName: 'Bar',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const initialState = {
|
|
||||||
index: 1,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
key: 'RouteA',
|
|
||||||
routeName: 'Foo',
|
|
||||||
params: { name: 'InitialParam', other: 'Unchanged' },
|
|
||||||
},
|
|
||||||
{ key: 'RouteB', routeName: 'Bar', params: {} },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const state = router.getStateForAction(
|
|
||||||
{
|
|
||||||
type: NavigationActions.SET_PARAMS,
|
|
||||||
params: { name: 'NewParam' },
|
|
||||||
key: 'RouteA',
|
|
||||||
},
|
|
||||||
initialState
|
|
||||||
);
|
|
||||||
expect(state.index).toEqual(1);
|
|
||||||
expect(state.routes[0].params).toEqual({
|
|
||||||
name: 'NewParam',
|
|
||||||
other: 'Unchanged',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getStateForAction returns null when navigating to same tab', () => {
|
|
||||||
const router = TabRouter(
|
|
||||||
{ Foo: BareLeafRouteConfig, Bar: BareLeafRouteConfig },
|
|
||||||
{ initialRouteName: 'Bar' }
|
|
||||||
);
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2).toEqual(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getStateForAction returns initial navigate', () => {
|
|
||||||
const router = TabRouter({
|
|
||||||
Foo: BareLeafRouteConfig,
|
|
||||||
Bar: BareLeafRouteConfig,
|
|
||||||
});
|
|
||||||
const state = router.getStateForAction({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Foo',
|
|
||||||
});
|
|
||||||
expect(state && state.index).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles nested tabs and nested actions', () => {
|
|
||||||
const ChildTabNavigator = () => <div />;
|
|
||||||
ChildTabNavigator.router = TabRouter({
|
|
||||||
Foo: BareLeafRouteConfig,
|
|
||||||
Bar: BareLeafRouteConfig,
|
|
||||||
});
|
|
||||||
const router = TabRouter({
|
|
||||||
Foo: BareLeafRouteConfig,
|
|
||||||
Baz: { screen: ChildTabNavigator },
|
|
||||||
Boo: BareLeafRouteConfig,
|
|
||||||
});
|
|
||||||
const params = { foo: '42' };
|
|
||||||
const action = router.getActionForPathAndParams('Baz/Bar', params);
|
|
||||||
const navAction = {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Baz',
|
|
||||||
params: { foo: '42' },
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
params: { foo: '42' },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
expect(action).toEqual(navAction);
|
|
||||||
const state = router.getStateForAction(navAction);
|
|
||||||
expect(state).toEqual({
|
|
||||||
index: 1,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
isTransitioning: false,
|
|
||||||
key: 'Baz',
|
|
||||||
routeName: 'Baz',
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'Bar',
|
|
||||||
routeName: 'Bar',
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'Boo',
|
|
||||||
routeName: 'Boo',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles passing params to nested tabs', () => {
|
|
||||||
const ChildTabNavigator = () => <div />;
|
|
||||||
ChildTabNavigator.router = TabRouter({
|
|
||||||
Boo: BareLeafRouteConfig,
|
|
||||||
Bar: BareLeafRouteConfig,
|
|
||||||
});
|
|
||||||
const router = TabRouter({
|
|
||||||
Foo: BareLeafRouteConfig,
|
|
||||||
Baz: { screen: ChildTabNavigator },
|
|
||||||
});
|
|
||||||
const navAction = {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Baz',
|
|
||||||
};
|
|
||||||
let state = router.getStateForAction(navAction);
|
|
||||||
expect(state).toEqual({
|
|
||||||
index: 1,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Baz',
|
|
||||||
routeName: 'Baz',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ensure that navigating back and forth doesn't overwrite
|
|
||||||
state = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Bar' },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
state = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Boo' },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state && state.routes[1]).toEqual({
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
key: 'Baz',
|
|
||||||
routeName: 'Baz',
|
|
||||||
routes: [
|
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles initial deep linking into nested tabs', () => {
|
|
||||||
const ChildTabNavigator = () => <div />;
|
|
||||||
ChildTabNavigator.router = TabRouter({
|
|
||||||
Foo: BareLeafRouteConfig,
|
|
||||||
Bar: BareLeafRouteConfig,
|
|
||||||
});
|
|
||||||
const router = TabRouter({
|
|
||||||
Foo: BareLeafRouteConfig,
|
|
||||||
Baz: { screen: ChildTabNavigator },
|
|
||||||
Boo: BareLeafRouteConfig,
|
|
||||||
});
|
|
||||||
const state = router.getStateForAction({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
});
|
|
||||||
expect(state).toEqual({
|
|
||||||
index: 1,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
key: 'Baz',
|
|
||||||
routeName: 'Baz',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Foo' },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2).toEqual({
|
|
||||||
index: 1,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Baz',
|
|
||||||
routeName: 'Baz',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Foo' },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3).toEqual(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles linking across of deeply nested tabs', () => {
|
|
||||||
const ChildNavigator0 = () => <div />;
|
|
||||||
ChildNavigator0.router = TabRouter({
|
|
||||||
Boo: BareLeafRouteConfig,
|
|
||||||
Baz: BareLeafRouteConfig,
|
|
||||||
});
|
|
||||||
const ChildNavigator1 = () => <div />;
|
|
||||||
ChildNavigator1.router = TabRouter({
|
|
||||||
Zoo: BareLeafRouteConfig,
|
|
||||||
Zap: BareLeafRouteConfig,
|
|
||||||
});
|
|
||||||
const MidNavigator = () => <div />;
|
|
||||||
MidNavigator.router = TabRouter({
|
|
||||||
Fee: { screen: ChildNavigator0 },
|
|
||||||
Bar: { screen: ChildNavigator1 },
|
|
||||||
});
|
|
||||||
const router = TabRouter({
|
|
||||||
Foo: { screen: MidNavigator },
|
|
||||||
Gah: BareLeafRouteConfig,
|
|
||||||
});
|
|
||||||
const state = router.getStateForAction(INIT_ACTION);
|
|
||||||
expect(state).toEqual({
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Fee',
|
|
||||||
routeName: 'Fee',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
|
||||||
{ key: 'Baz', routeName: 'Baz' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Bar',
|
|
||||||
routeName: 'Bar',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Zoo', routeName: 'Zoo' },
|
|
||||||
{ key: 'Zap', routeName: 'Zap' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Gah', routeName: 'Gah' },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Zap' },
|
|
||||||
state
|
|
||||||
);
|
|
||||||
expect(state2).toEqual({
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Fee',
|
|
||||||
routeName: 'Fee',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
|
||||||
{ key: 'Baz', routeName: 'Baz' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
key: 'Bar',
|
|
||||||
routeName: 'Bar',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Zoo', routeName: 'Zoo' },
|
|
||||||
{ key: 'Zap', routeName: 'Zap' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Gah', routeName: 'Gah' },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'Zap' },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
expect(state3).toEqual(null);
|
|
||||||
const state4 = router.getStateForAction({
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Foo',
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Bar',
|
|
||||||
action: {
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
routeName: 'Zap',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(state4).toEqual({
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: 'Fee',
|
|
||||||
routeName: 'Fee',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
|
||||||
{ key: 'Baz', routeName: 'Baz' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
key: 'Bar',
|
|
||||||
routeName: 'Bar',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Zoo', routeName: 'Zoo' },
|
|
||||||
{ key: 'Zap', routeName: 'Zap' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Gah', routeName: 'Gah' },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.only('Handles path configuration', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
const router = TabRouter({
|
|
||||||
Foo: {
|
|
||||||
path: 'f',
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
path: 'b/:great',
|
|
||||||
screen: ScreenB,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const params = { foo: '42' };
|
|
||||||
const action = router.getActionForPathAndParams('b/anything', params);
|
|
||||||
const expectedAction = {
|
|
||||||
params: {
|
|
||||||
foo: '42',
|
|
||||||
great: 'anything',
|
|
||||||
},
|
|
||||||
routeName: 'Bar',
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
};
|
|
||||||
expect(action).toEqual(expectedAction);
|
|
||||||
|
|
||||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
|
||||||
const expectedState = {
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo' },
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
expect(state).toEqual(expectedState);
|
|
||||||
const state2 = router.getStateForAction(expectedAction, state);
|
|
||||||
const expectedState2 = {
|
|
||||||
index: 1,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
|
||||||
{
|
|
||||||
key: 'Bar',
|
|
||||||
routeName: 'Bar',
|
|
||||||
params: { foo: '42', great: 'anything' },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
expect(state2).toEqual(expectedState2);
|
|
||||||
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
|
|
||||||
expect(router.getComponentForState(expectedState2)).toEqual(ScreenB);
|
|
||||||
expect(router.getPathAndParamsForState(expectedState).path).toEqual('f');
|
|
||||||
expect(router.getPathAndParamsForState(expectedState2).path).toEqual(
|
|
||||||
'b/anything'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Handles default configuration', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
const router = TabRouter({
|
|
||||||
Foo: {
|
|
||||||
path: '',
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
path: 'b',
|
|
||||||
screen: ScreenB,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const action = router.getActionForPathAndParams('', { foo: '42' });
|
|
||||||
expect(action).toEqual({
|
|
||||||
params: {
|
|
||||||
foo: '42',
|
|
||||||
},
|
|
||||||
routeName: 'Foo',
|
|
||||||
type: NavigationActions.NAVIGATE,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Gets deep path', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
ScreenA.router = TabRouter({
|
|
||||||
Boo: { screen: ScreenB },
|
|
||||||
Baz: { screen: ScreenB },
|
|
||||||
});
|
|
||||||
const router = TabRouter({
|
|
||||||
Foo: {
|
|
||||||
path: 'f',
|
|
||||||
screen: ScreenA,
|
|
||||||
},
|
|
||||||
Bar: {
|
|
||||||
screen: ScreenB,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
key: 'Foo',
|
|
||||||
routeName: 'Foo',
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'Boo', routeName: 'Boo' },
|
|
||||||
{ key: 'Baz', routeName: 'Baz' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ key: 'Bar', routeName: 'Bar' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const { path } = router.getPathAndParamsForState(state);
|
|
||||||
expect(path).toEqual('f/Baz');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Can navigate to other tab (no router) with params', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
|
|
||||||
const router = TabRouter({
|
|
||||||
a: { screen: ScreenA },
|
|
||||||
b: { screen: ScreenB },
|
|
||||||
});
|
|
||||||
|
|
||||||
const state0 = router.getStateForAction(INIT_ACTION);
|
|
||||||
|
|
||||||
expect(state0).toEqual({
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [{ key: 'a', routeName: 'a' }, { key: 'b', routeName: 'b' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
const params = { key: 'value' };
|
|
||||||
|
|
||||||
const state1 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'b', params },
|
|
||||||
state0
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(state1).toEqual({
|
|
||||||
index: 1,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [
|
|
||||||
{ key: 'a', routeName: 'a' },
|
|
||||||
{ key: 'b', routeName: 'b', params },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Back actions are not propagated to inactive children', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
const ScreenC = () => <div />;
|
|
||||||
const InnerNavigator = () => <div />;
|
|
||||||
InnerNavigator.router = TabRouter({
|
|
||||||
a: { screen: ScreenA },
|
|
||||||
b: { screen: ScreenB },
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = TabRouter(
|
|
||||||
{
|
|
||||||
inner: { screen: InnerNavigator },
|
|
||||||
c: { screen: ScreenC },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
backBehavior: 'none',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const state0 = router.getStateForAction(INIT_ACTION);
|
|
||||||
|
|
||||||
const state1 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'b' },
|
|
||||||
state0
|
|
||||||
);
|
|
||||||
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'c' },
|
|
||||||
state1
|
|
||||||
);
|
|
||||||
|
|
||||||
const state3 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.BACK },
|
|
||||||
state2
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(state3).toEqual(state2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Back behavior initialRoute works', () => {
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
const router = TabRouter({
|
|
||||||
a: { screen: ScreenA },
|
|
||||||
b: { screen: ScreenB },
|
|
||||||
});
|
|
||||||
|
|
||||||
const state0 = router.getStateForAction(INIT_ACTION);
|
|
||||||
|
|
||||||
const state1 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.NAVIGATE, routeName: 'b' },
|
|
||||||
state0
|
|
||||||
);
|
|
||||||
|
|
||||||
const state2 = router.getStateForAction(
|
|
||||||
{ type: NavigationActions.BACK },
|
|
||||||
state1
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(state2).toEqual(state0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Inner actions are only unpacked if the current tab matches', () => {
|
|
||||||
const PlainScreen = () => <div />;
|
|
||||||
const ScreenA = () => <div />;
|
|
||||||
const ScreenB = () => <div />;
|
|
||||||
ScreenB.router = TabRouter({
|
|
||||||
Baz: { screen: PlainScreen },
|
|
||||||
Zoo: { screen: PlainScreen },
|
|
||||||
});
|
|
||||||
ScreenA.router = TabRouter({
|
|
||||||
Bar: { screen: PlainScreen },
|
|
||||||
Boo: { screen: ScreenB },
|
|
||||||
});
|
|
||||||
const router = TabRouter({
|
|
||||||
Foo: { screen: ScreenA },
|
|
||||||
});
|
|
||||||
const screenApreState = {
|
|
||||||
index: 0,
|
|
||||||
key: 'Foo',
|
|
||||||
isTransitioning: false,
|
|
||||||
routeName: 'Foo',
|
|
||||||
routes: [{ key: 'Bar', routeName: 'Bar' }],
|
|
||||||
};
|
|
||||||
const preState = {
|
|
||||||
index: 0,
|
|
||||||
isTransitioning: false,
|
|
||||||
routes: [screenApreState],
|
|
||||||
};
|
|
||||||
|
|
||||||
const comparable = state => {
|
|
||||||
let result = {};
|
|
||||||
if (typeof state.routeName === 'string') {
|
|
||||||
result = { ...result, routeName: state.routeName };
|
|
||||||
}
|
|
||||||
if (state.routes instanceof Array) {
|
|
||||||
result = {
|
|
||||||
...result,
|
|
||||||
routes: state.routes.map(comparable),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const action = NavigationActions.navigate({
|
|
||||||
routeName: 'Boo',
|
|
||||||
action: NavigationActions.navigate({ routeName: 'Zoo' }),
|
|
||||||
});
|
|
||||||
const expectedState = ScreenA.router.getStateForAction(
|
|
||||||
action,
|
|
||||||
screenApreState
|
|
||||||
);
|
|
||||||
const state = router.getStateForAction(action, preState);
|
|
||||||
const innerState = state ? state.routes[0] : state;
|
|
||||||
|
|
||||||
expect(innerState.routes[1].index).toEqual(1);
|
|
||||||
expect(expectedState && comparable(expectedState)).toEqual(
|
|
||||||
innerState && comparable(innerState)
|
|
||||||
);
|
|
||||||
|
|
||||||
const noMatchAction = NavigationActions.navigate({
|
|
||||||
routeName: 'Qux',
|
|
||||||
action: NavigationActions.navigate({ routeName: 'Zoo' }),
|
|
||||||
});
|
|
||||||
const expectedState2 = ScreenA.router.getStateForAction(
|
|
||||||
noMatchAction,
|
|
||||||
screenApreState
|
|
||||||
);
|
|
||||||
const state2 = router.getStateForAction(noMatchAction, preState);
|
|
||||||
const innerState2 = state2 ? state2.routes[0] : state2;
|
|
||||||
|
|
||||||
expect(innerState2.routes[1].index).toEqual(0);
|
|
||||||
expect(expectedState2 && comparable(expectedState2)).toEqual(
|
|
||||||
innerState2 && comparable(innerState2)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,37 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`validateRouteConfigMap Fails if both screen and getScreen are defined 1`] = `"Route 'Home' should declare a screen or a getScreen, not both."`;
|
|
||||||
|
|
||||||
exports[`validateRouteConfigMap Fails on bad object 1`] = `
|
|
||||||
"The component for route 'Home' must be a React component. For example:
|
|
||||||
|
|
||||||
import MyScreen from './MyScreen';
|
|
||||||
...
|
|
||||||
Home: MyScreen,
|
|
||||||
}
|
|
||||||
|
|
||||||
You can also use a navigator:
|
|
||||||
|
|
||||||
import MyNavigator from './MyNavigator';
|
|
||||||
...
|
|
||||||
Home: MyNavigator,
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`validateRouteConfigMap Fails on empty bare screen 1`] = `
|
|
||||||
"The component for route 'Home' must be a React component. For example:
|
|
||||||
|
|
||||||
import MyScreen from './MyScreen';
|
|
||||||
...
|
|
||||||
Home: MyScreen,
|
|
||||||
}
|
|
||||||
|
|
||||||
You can also use a navigator:
|
|
||||||
|
|
||||||
import MyNavigator from './MyNavigator';
|
|
||||||
...
|
|
||||||
Home: MyNavigator,
|
|
||||||
}"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`validateRouteConfigMap Fails on empty config 1`] = `"Please specify at least one route when configuring a navigator."`;
|
|
@ -1,176 +0,0 @@
|
|||||||
import { Component } from 'react';
|
|
||||||
import createConfigGetter from '../createConfigGetter';
|
|
||||||
|
|
||||||
const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
|
|
||||||
remove: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should get config for screen', () => {
|
|
||||||
/* eslint-disable react/no-multi-comp */
|
|
||||||
|
|
||||||
class HomeScreen extends Component {
|
|
||||||
static navigationOptions = ({ navigation }) => ({
|
|
||||||
title: `Welcome ${
|
|
||||||
navigation.state.params ? navigation.state.params.user : 'anonymous'
|
|
||||||
}`,
|
|
||||||
gesturesEnabled: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SettingsScreen extends Component {
|
|
||||||
static navigationOptions = {
|
|
||||||
title: 'Settings!!!',
|
|
||||||
gesturesEnabled: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NotificationScreen extends Component {
|
|
||||||
static navigationOptions = ({ navigation }) => ({
|
|
||||||
title: '42',
|
|
||||||
gesturesEnabled: navigation.state.params
|
|
||||||
? !navigation.state.params.fullscreen
|
|
||||||
: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getScreenOptions = createConfigGetter({
|
|
||||||
Home: { screen: HomeScreen },
|
|
||||||
Settings: { screen: SettingsScreen },
|
|
||||||
Notifications: {
|
|
||||||
screen: NotificationScreen,
|
|
||||||
navigationOptions: {
|
|
||||||
title: '10 new notifications',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{ key: 'A', routeName: 'Home' },
|
|
||||||
{ key: 'B', routeName: 'Home', params: { user: 'jane' } },
|
|
||||||
{ key: 'C', routeName: 'Settings' },
|
|
||||||
{ key: 'D', routeName: 'Notifications' },
|
|
||||||
{ key: 'E', routeName: 'Notifications', params: { fullscreen: true } },
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(
|
|
||||||
getScreenOptions(
|
|
||||||
{
|
|
||||||
state: routes[0],
|
|
||||||
dispatch: () => false,
|
|
||||||
addListener: dummyEventSubscriber,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
).title
|
|
||||||
).toEqual('Welcome anonymous');
|
|
||||||
expect(
|
|
||||||
getScreenOptions(
|
|
||||||
{
|
|
||||||
state: routes[1],
|
|
||||||
dispatch: () => false,
|
|
||||||
addListener: dummyEventSubscriber,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
).title
|
|
||||||
).toEqual('Welcome jane');
|
|
||||||
expect(
|
|
||||||
getScreenOptions(
|
|
||||||
{
|
|
||||||
state: routes[0],
|
|
||||||
dispatch: () => false,
|
|
||||||
addListener: dummyEventSubscriber,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
).gesturesEnabled
|
|
||||||
).toEqual(true);
|
|
||||||
expect(
|
|
||||||
getScreenOptions(
|
|
||||||
{
|
|
||||||
state: routes[2],
|
|
||||||
dispatch: () => false,
|
|
||||||
addListener: dummyEventSubscriber,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
).title
|
|
||||||
).toEqual('Settings!!!');
|
|
||||||
expect(
|
|
||||||
getScreenOptions(
|
|
||||||
{
|
|
||||||
state: routes[2],
|
|
||||||
dispatch: () => false,
|
|
||||||
addListener: dummyEventSubscriber,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
).gesturesEnabled
|
|
||||||
).toEqual(false);
|
|
||||||
expect(
|
|
||||||
getScreenOptions(
|
|
||||||
{
|
|
||||||
state: routes[3],
|
|
||||||
dispatch: () => false,
|
|
||||||
addListener: dummyEventSubscriber,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
).title
|
|
||||||
).toEqual('10 new notifications');
|
|
||||||
expect(
|
|
||||||
getScreenOptions(
|
|
||||||
{
|
|
||||||
state: routes[3],
|
|
||||||
dispatch: () => false,
|
|
||||||
addListener: dummyEventSubscriber,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
).gesturesEnabled
|
|
||||||
).toEqual(true);
|
|
||||||
expect(
|
|
||||||
getScreenOptions(
|
|
||||||
{
|
|
||||||
state: routes[4],
|
|
||||||
dispatch: () => false,
|
|
||||||
addListener: dummyEventSubscriber,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
).gesturesEnabled
|
|
||||||
).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw if the route does not exist', () => {
|
|
||||||
/* eslint-disable react/no-multi-comp */
|
|
||||||
|
|
||||||
const HomeScreen = () => null;
|
|
||||||
HomeScreen.navigationOptions = {
|
|
||||||
title: 'Home screen',
|
|
||||||
gesturesEnabled: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const getScreenOptions = createConfigGetter({
|
|
||||||
Home: { screen: HomeScreen },
|
|
||||||
});
|
|
||||||
|
|
||||||
const routes = [{ key: 'B', routeName: 'Settings' }];
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
getScreenOptions(
|
|
||||||
{
|
|
||||||
state: routes[0],
|
|
||||||
dispatch: () => false,
|
|
||||||
addListener: dummyEventSubscriber,
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
).toThrowError(
|
|
||||||
"There is no route defined for key Settings.\nMust be one of: 'Home'"
|
|
||||||
);
|
|
||||||
});
|
|
@ -1,34 +0,0 @@
|
|||||||
import { urlToPathAndParams } from '../pathUtils';
|
|
||||||
|
|
||||||
test('urlToPathAndParams empty', () => {
|
|
||||||
const { path, params } = urlToPathAndParams('foo://');
|
|
||||||
expect(path).toBe('');
|
|
||||||
expect(params).toEqual({});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('urlToPathAndParams empty params', () => {
|
|
||||||
const { path, params } = urlToPathAndParams('foo://foo/bar/b');
|
|
||||||
expect(path).toBe('foo/bar/b');
|
|
||||||
expect(params).toEqual({});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('urlToPathAndParams trailing slash', () => {
|
|
||||||
const { path, params } = urlToPathAndParams('foo://foo/bar/');
|
|
||||||
expect(path).toBe('foo/bar');
|
|
||||||
expect(params).toEqual({});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('urlToPathAndParams with params', () => {
|
|
||||||
const { path, params } = urlToPathAndParams('foo://foo/bar?asdf=1&dude=foo');
|
|
||||||
expect(path).toBe('foo/bar');
|
|
||||||
expect(params).toEqual({ asdf: '1', dude: 'foo' });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('urlToPathAndParams with custom delimeter', () => {
|
|
||||||
const { path, params } = urlToPathAndParams(
|
|
||||||
'https://example.com/foo/bar?asdf=1',
|
|
||||||
'https://example.com/'
|
|
||||||
);
|
|
||||||
expect(path).toBe('foo/bar');
|
|
||||||
expect(params).toEqual({ asdf: '1' });
|
|
||||||
});
|
|
@ -1,60 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import validateRouteConfigMap from '../validateRouteConfigMap';
|
|
||||||
import StackRouter from '../StackRouter';
|
|
||||||
|
|
||||||
const ListScreen = () => <div />;
|
|
||||||
|
|
||||||
const ProfileNavigator = () => <div />;
|
|
||||||
ProfileNavigator.router = StackRouter({
|
|
||||||
list: {
|
|
||||||
screen: ListScreen,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('validateRouteConfigMap', () => {
|
|
||||||
test('Fails on empty bare screen', () => {
|
|
||||||
const invalidMap = {
|
|
||||||
Home: undefined,
|
|
||||||
};
|
|
||||||
expect(() =>
|
|
||||||
validateRouteConfigMap(invalidMap)
|
|
||||||
).toThrowErrorMatchingSnapshot();
|
|
||||||
});
|
|
||||||
test('Fails on empty config', () => {
|
|
||||||
const invalidMap = {};
|
|
||||||
expect(() =>
|
|
||||||
validateRouteConfigMap(invalidMap)
|
|
||||||
).toThrowErrorMatchingSnapshot();
|
|
||||||
});
|
|
||||||
test('Fails on bad object', () => {
|
|
||||||
const invalidMap = {
|
|
||||||
Home: {
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
expect(() =>
|
|
||||||
validateRouteConfigMap(invalidMap)
|
|
||||||
).toThrowErrorMatchingSnapshot();
|
|
||||||
});
|
|
||||||
test('Fails if both screen and getScreen are defined', () => {
|
|
||||||
const invalidMap = {
|
|
||||||
Home: {
|
|
||||||
screen: ListScreen,
|
|
||||||
getScreen: () => ListScreen,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
expect(() =>
|
|
||||||
validateRouteConfigMap(invalidMap)
|
|
||||||
).toThrowErrorMatchingSnapshot();
|
|
||||||
});
|
|
||||||
test('Succeeds on a valid config', () => {
|
|
||||||
const validMap = {
|
|
||||||
Home: {
|
|
||||||
screen: ProfileNavigator,
|
|
||||||
},
|
|
||||||
Chat: ListScreen,
|
|
||||||
};
|
|
||||||
validateRouteConfigMap(validMap);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,58 +0,0 @@
|
|||||||
import invariant from '../utils/invariant';
|
|
||||||
|
|
||||||
import getScreenForRouteName from './getScreenForRouteName';
|
|
||||||
import validateScreenOptions from './validateScreenOptions';
|
|
||||||
|
|
||||||
function applyConfig(configurer, navigationOptions, configProps) {
|
|
||||||
if (typeof configurer === 'function') {
|
|
||||||
return {
|
|
||||||
...navigationOptions,
|
|
||||||
...configurer({
|
|
||||||
...configProps,
|
|
||||||
navigationOptions,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (typeof configurer === 'object') {
|
|
||||||
return {
|
|
||||||
...navigationOptions,
|
|
||||||
...configurer,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return navigationOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (routeConfigs, navigatorScreenConfig) => (
|
|
||||||
navigation,
|
|
||||||
screenProps
|
|
||||||
) => {
|
|
||||||
const { state } = navigation;
|
|
||||||
const route = state;
|
|
||||||
|
|
||||||
invariant(
|
|
||||||
route.routeName && typeof route.routeName === 'string',
|
|
||||||
'Cannot get config because the route does not have a routeName.'
|
|
||||||
);
|
|
||||||
|
|
||||||
const Component = getScreenForRouteName(routeConfigs, route.routeName);
|
|
||||||
|
|
||||||
const routeConfig = routeConfigs[route.routeName];
|
|
||||||
|
|
||||||
const routeScreenConfig =
|
|
||||||
routeConfig === Component ? null : routeConfig.navigationOptions;
|
|
||||||
const componentScreenConfig = Component.navigationOptions;
|
|
||||||
|
|
||||||
const configOptions = { navigation, screenProps: screenProps || {} };
|
|
||||||
|
|
||||||
let outputConfig = applyConfig(navigatorScreenConfig, {}, configOptions);
|
|
||||||
outputConfig = applyConfig(
|
|
||||||
componentScreenConfig,
|
|
||||||
outputConfig,
|
|
||||||
configOptions
|
|
||||||
);
|
|
||||||
outputConfig = applyConfig(routeScreenConfig, outputConfig, configOptions);
|
|
||||||
|
|
||||||
validateScreenOptions(outputConfig, route);
|
|
||||||
|
|
||||||
return outputConfig;
|
|
||||||
};
|
|
@ -1,46 +0,0 @@
|
|||||||
import NavigationActions from '../NavigationActions';
|
|
||||||
import invariant from '../utils/invariant';
|
|
||||||
|
|
||||||
const getNavigationActionCreators = route => {
|
|
||||||
return {
|
|
||||||
goBack: key => {
|
|
||||||
let actualizedKey = key;
|
|
||||||
if (key === undefined && route.key) {
|
|
||||||
invariant(typeof route.key === 'string', 'key should be a string');
|
|
||||||
actualizedKey = route.key;
|
|
||||||
}
|
|
||||||
return NavigationActions.back({ key: actualizedKey });
|
|
||||||
},
|
|
||||||
navigate: (navigateTo, params, action) => {
|
|
||||||
if (typeof navigateTo === 'string') {
|
|
||||||
return NavigationActions.navigate({
|
|
||||||
routeName: navigateTo,
|
|
||||||
params,
|
|
||||||
action,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
invariant(
|
|
||||||
typeof navigateTo === 'object',
|
|
||||||
'Must navigateTo an object or a string'
|
|
||||||
);
|
|
||||||
invariant(
|
|
||||||
params == null,
|
|
||||||
'Params must not be provided to .navigate() when specifying an object'
|
|
||||||
);
|
|
||||||
invariant(
|
|
||||||
action == null,
|
|
||||||
'Child action must not be provided to .navigate() when specifying an object'
|
|
||||||
);
|
|
||||||
return NavigationActions.navigate(navigateTo);
|
|
||||||
},
|
|
||||||
setParams: params => {
|
|
||||||
invariant(
|
|
||||||
route.key && typeof route.key === 'string',
|
|
||||||
'setParams cannot be called by root navigator'
|
|
||||||
);
|
|
||||||
return NavigationActions.setParams({ params, key: route.key });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getNavigationActionCreators;
|
|
@ -1,36 +0,0 @@
|
|||||||
import invariant from '../utils/invariant';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple helper that gets a single screen (React component or navigator)
|
|
||||||
* out of the navigator config.
|
|
||||||
*/
|
|
||||||
export default function getScreenForRouteName(routeConfigs, routeName) {
|
|
||||||
const routeConfig = routeConfigs[routeName];
|
|
||||||
|
|
||||||
if (!routeConfig) {
|
|
||||||
throw new Error(
|
|
||||||
`There is no route defined for key ${routeName}.\n` +
|
|
||||||
`Must be one of: ${Object.keys(routeConfigs)
|
|
||||||
.map(a => `'${a}'`)
|
|
||||||
.join(',')}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (routeConfig.screen) {
|
|
||||||
return routeConfig.screen;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof routeConfig.getScreen === 'function') {
|
|
||||||
const screen = routeConfig.getScreen();
|
|
||||||
invariant(
|
|
||||||
typeof screen === 'function',
|
|
||||||
`The getScreen defined for route '${routeName} didn't return a valid ` +
|
|
||||||
'screen or navigator.\n\n' +
|
|
||||||
'Please pass it like this:\n' +
|
|
||||||
`${routeName}: {\n getScreen: () => require('./MyScreen').default\n}`
|
|
||||||
);
|
|
||||||
return screen;
|
|
||||||
}
|
|
||||||
|
|
||||||
return routeConfig;
|
|
||||||
}
|
|
@ -1,223 +0,0 @@
|
|||||||
import pathToRegexp, { compile } from 'path-to-regexp';
|
|
||||||
import NavigationActions from '../NavigationActions';
|
|
||||||
import invariant from '../utils/invariant';
|
|
||||||
|
|
||||||
const queryString = require('query-string');
|
|
||||||
|
|
||||||
function isEmpty(obj) {
|
|
||||||
if (!obj) return true;
|
|
||||||
for (let key in obj) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getParamsFromPath = (inputParams, pathMatch, pathMatchKeys) => {
|
|
||||||
const params = pathMatch.slice(1).reduce(
|
|
||||||
// iterate over matched path params
|
|
||||||
(paramsOut, matchResult, i) => {
|
|
||||||
const key = pathMatchKeys[i];
|
|
||||||
if (!key || key.asterisk) {
|
|
||||||
return paramsOut;
|
|
||||||
}
|
|
||||||
const paramName = key.name;
|
|
||||||
|
|
||||||
let decodedMatchResult;
|
|
||||||
try {
|
|
||||||
decodedMatchResult = decodeURIComponent(matchResult);
|
|
||||||
} catch (e) {
|
|
||||||
// ignore `URIError: malformed URI`
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsOut[paramName] = decodedMatchResult || matchResult;
|
|
||||||
return paramsOut;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// start with the input(query string) params, which will get overridden by path params
|
|
||||||
...inputParams,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return params;
|
|
||||||
};
|
|
||||||
const getRestOfPath = (pathMatch, pathMatchKeys) => {
|
|
||||||
const rest = pathMatch[pathMatchKeys.findIndex(k => k.asterisk) + 1];
|
|
||||||
return rest;
|
|
||||||
};
|
|
||||||
export const urlToPathAndParams = (url, uriPrefix) => {
|
|
||||||
const searchMatch = url.match(/^(.*)\?(.*)$/);
|
|
||||||
const params = searchMatch ? queryString.parse(searchMatch[2]) : {};
|
|
||||||
const urlWithoutSearch = searchMatch ? searchMatch[1] : url;
|
|
||||||
const delimiter = uriPrefix || '://';
|
|
||||||
let path = urlWithoutSearch.split(delimiter)[1];
|
|
||||||
if (path === undefined) {
|
|
||||||
path = urlWithoutSearch;
|
|
||||||
}
|
|
||||||
if (path === '/') {
|
|
||||||
path = '';
|
|
||||||
}
|
|
||||||
if (path[path.length - 1] === '/') {
|
|
||||||
path = path.slice(0, -1);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
path,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createPathParser = (
|
|
||||||
childRouters,
|
|
||||||
routeConfigs,
|
|
||||||
{ paths: pathConfigs = {}, disableRouteNamePaths }
|
|
||||||
) => {
|
|
||||||
const pathsByRouteNames = {};
|
|
||||||
let paths = [];
|
|
||||||
|
|
||||||
// Build pathsByRouteNames, which includes a regex to match paths for each route. Keep in mind, the regex will pass for the route and all child routes. The code that uses pathsByRouteNames will need to also verify that the child router produces an action, in the case of isPathMatchable false (a null path).
|
|
||||||
Object.keys(childRouters).forEach(routeName => {
|
|
||||||
let pathPattern;
|
|
||||||
|
|
||||||
// First check for paths on the router, then check the route config
|
|
||||||
if (pathConfigs[routeName] !== undefined) {
|
|
||||||
pathPattern = pathConfigs[routeName];
|
|
||||||
} else {
|
|
||||||
pathPattern = routeConfigs[routeName].path;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pathPattern === undefined) {
|
|
||||||
// If the user hasn't specified a path at all nor disableRouteNamePaths, then we assume the routeName is an appropriate path
|
|
||||||
pathPattern = disableRouteNamePaths ? null : routeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
invariant(
|
|
||||||
pathPattern === null || typeof pathPattern === 'string',
|
|
||||||
`Route path for ${routeName} must be specified as a string, or null.`
|
|
||||||
);
|
|
||||||
|
|
||||||
// the path may be specified as null, which is similar to empty string because it allows child routers to handle the action, but it will not match empty paths
|
|
||||||
const isPathMatchable = pathPattern !== null;
|
|
||||||
// pathPattern is a string with inline params, such as people/:id/*foo
|
|
||||||
const exactReKeys = [];
|
|
||||||
const exactRe = isPathMatchable
|
|
||||||
? pathToRegexp(pathPattern, exactReKeys)
|
|
||||||
: null;
|
|
||||||
const extendedPathReKeys = [];
|
|
||||||
const isWildcard = pathPattern === '' || !isPathMatchable;
|
|
||||||
const extendedPathRe = pathToRegexp(
|
|
||||||
isWildcard ? '*' : `${pathPattern}/*`,
|
|
||||||
extendedPathReKeys
|
|
||||||
);
|
|
||||||
|
|
||||||
pathsByRouteNames[routeName] = {
|
|
||||||
exactRe,
|
|
||||||
exactReKeys,
|
|
||||||
extendedPathRe,
|
|
||||||
extendedPathReKeys,
|
|
||||||
isWildcard,
|
|
||||||
toPath: pathPattern === null ? () => '' : compile(pathPattern),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
paths = Object.entries(pathsByRouteNames);
|
|
||||||
|
|
||||||
const getActionForPathAndParams = (pathToResolve = '', inputParams = {}) => {
|
|
||||||
// Attempt to match `pathToResolve` with a route in this router's routeConfigs, deferring to child routers
|
|
||||||
|
|
||||||
let matchedAction = null;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const [routeName, path] of paths) {
|
|
||||||
const { exactRe, exactReKeys, extendedPathRe, extendedPathReKeys } = path;
|
|
||||||
const childRouter = childRouters[routeName];
|
|
||||||
|
|
||||||
const exactMatch = exactRe && exactRe.exec(pathToResolve);
|
|
||||||
|
|
||||||
if (exactMatch && exactMatch.length) {
|
|
||||||
const extendedMatch =
|
|
||||||
extendedPathRe && extendedPathRe.exec(pathToResolve);
|
|
||||||
let childAction = null;
|
|
||||||
if (extendedMatch && childRouter) {
|
|
||||||
const restOfPath = getRestOfPath(extendedMatch, extendedPathReKeys);
|
|
||||||
childAction = childRouter.getActionForPathAndParams(
|
|
||||||
restOfPath,
|
|
||||||
inputParams
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NavigationActions.navigate({
|
|
||||||
routeName,
|
|
||||||
params: getParamsFromPath(inputParams, exactMatch, exactReKeys),
|
|
||||||
action: childAction,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const [routeName, path] of paths) {
|
|
||||||
const { extendedPathRe, extendedPathReKeys } = path;
|
|
||||||
const childRouter = childRouters[routeName];
|
|
||||||
|
|
||||||
const extendedMatch =
|
|
||||||
extendedPathRe && extendedPathRe.exec(pathToResolve);
|
|
||||||
|
|
||||||
if (extendedMatch && extendedMatch.length) {
|
|
||||||
const restOfPath = getRestOfPath(extendedMatch, extendedPathReKeys);
|
|
||||||
let childAction = null;
|
|
||||||
if (childRouter) {
|
|
||||||
childAction = childRouter.getActionForPathAndParams(
|
|
||||||
restOfPath,
|
|
||||||
inputParams
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!childAction) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return NavigationActions.navigate({
|
|
||||||
routeName,
|
|
||||||
params: getParamsFromPath(
|
|
||||||
inputParams,
|
|
||||||
extendedMatch,
|
|
||||||
extendedPathReKeys
|
|
||||||
),
|
|
||||||
action: childAction,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
const getPathAndParamsForRoute = route => {
|
|
||||||
const { routeName, params } = route;
|
|
||||||
const childRouter = childRouters[routeName];
|
|
||||||
const { toPath, exactReKeys } = pathsByRouteNames[routeName];
|
|
||||||
const subPath = toPath(params);
|
|
||||||
const nonPathParams = {};
|
|
||||||
if (params) {
|
|
||||||
Object.keys(params)
|
|
||||||
.filter(paramName => !exactReKeys.find(k => k.name === paramName))
|
|
||||||
.forEach(paramName => {
|
|
||||||
nonPathParams[paramName] = params[paramName];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (childRouter) {
|
|
||||||
// If it has a router it's a navigator.
|
|
||||||
// If it doesn't have router it's an ordinary React component.
|
|
||||||
const child = childRouter.getPathAndParamsForState(route);
|
|
||||||
return {
|
|
||||||
path: subPath ? `${subPath}/${child.path}` : child.path,
|
|
||||||
params: child.params
|
|
||||||
? { ...nonPathParams, ...child.params }
|
|
||||||
: nonPathParams,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
path: subPath,
|
|
||||||
params: nonPathParams,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
return { getActionForPathAndParams, getPathAndParamsForRoute };
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getParamsFromPath,
|
|
||||||
createPathParser,
|
|
||||||
};
|
|
@ -1,55 +0,0 @@
|
|||||||
import invariant from '../utils/invariant';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make sure the config passed e.g. to StackRouter, TabRouter has
|
|
||||||
* the correct format, and throw a clear error if it doesn't.
|
|
||||||
*/
|
|
||||||
function validateRouteConfigMap(routeConfigs) {
|
|
||||||
const routeNames = Object.keys(routeConfigs);
|
|
||||||
invariant(
|
|
||||||
routeNames.length > 0,
|
|
||||||
'Please specify at least one route when configuring a navigator.'
|
|
||||||
);
|
|
||||||
|
|
||||||
routeNames.forEach(routeName => {
|
|
||||||
const routeConfig = routeConfigs[routeName];
|
|
||||||
const screenComponent = getScreenComponent(routeConfig);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!screenComponent ||
|
|
||||||
(typeof screenComponent !== 'function' &&
|
|
||||||
typeof screenComponent !== 'string' &&
|
|
||||||
!routeConfig.getScreen)
|
|
||||||
) {
|
|
||||||
throw new Error(`The component for route '${routeName}' must be a React component. For example:
|
|
||||||
|
|
||||||
import MyScreen from './MyScreen';
|
|
||||||
...
|
|
||||||
${routeName}: MyScreen,
|
|
||||||
}
|
|
||||||
|
|
||||||
You can also use a navigator:
|
|
||||||
|
|
||||||
import MyNavigator from './MyNavigator';
|
|
||||||
...
|
|
||||||
${routeName}: MyNavigator,
|
|
||||||
}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (routeConfig.screen && routeConfig.getScreen) {
|
|
||||||
throw new Error(
|
|
||||||
`Route '${routeName}' should declare a screen or a getScreen, not both.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScreenComponent(routeConfig) {
|
|
||||||
if (!routeConfig) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return routeConfig.screen ? routeConfig.screen : routeConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default validateRouteConfigMap;
|
|
@ -1,78 +0,0 @@
|
|||||||
const deprecatedKeys = ['tabBar'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make sure screen options returned by the `getScreenOption`
|
|
||||||
* are valid
|
|
||||||
*/
|
|
||||||
export default (screenOptions, route) => {
|
|
||||||
const keys = Object.keys(screenOptions);
|
|
||||||
|
|
||||||
const deprecatedKey = keys.find(key => deprecatedKeys.includes(key));
|
|
||||||
|
|
||||||
if (typeof screenOptions.title === 'function') {
|
|
||||||
throw new Error(
|
|
||||||
[
|
|
||||||
`\`title\` cannot be defined as a function in navigation options for \`${
|
|
||||||
route.routeName
|
|
||||||
}\` screen. \n`,
|
|
||||||
'Try replacing the following:',
|
|
||||||
'{',
|
|
||||||
' title: ({ state }) => state...',
|
|
||||||
'}',
|
|
||||||
'',
|
|
||||||
'with:',
|
|
||||||
'({ navigation }) => ({',
|
|
||||||
' title: navigation.state...',
|
|
||||||
'})',
|
|
||||||
].join('\n')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deprecatedKey && typeof screenOptions[deprecatedKey] === 'function') {
|
|
||||||
throw new Error(
|
|
||||||
[
|
|
||||||
`\`${deprecatedKey}\` cannot be defined as a function in navigation options for \`${
|
|
||||||
route.routeName
|
|
||||||
}\` screen. \n`,
|
|
||||||
'Try replacing the following:',
|
|
||||||
'{',
|
|
||||||
` ${deprecatedKey}: ({ state }) => ({`,
|
|
||||||
' key: state...',
|
|
||||||
' })',
|
|
||||||
'}',
|
|
||||||
'',
|
|
||||||
'with:',
|
|
||||||
'({ navigation }) => ({',
|
|
||||||
` ${deprecatedKey}Key: navigation.state...`,
|
|
||||||
'})',
|
|
||||||
].join('\n')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deprecatedKey && typeof screenOptions[deprecatedKey] === 'object') {
|
|
||||||
throw new Error(
|
|
||||||
[
|
|
||||||
`Invalid key \`${deprecatedKey}\` defined in navigation options for \`${
|
|
||||||
route.routeName
|
|
||||||
}\` screen.`,
|
|
||||||
'\n',
|
|
||||||
'Try replacing the following navigation options:',
|
|
||||||
'{',
|
|
||||||
` ${deprecatedKey}: {`,
|
|
||||||
...Object.keys(screenOptions[deprecatedKey]).map(
|
|
||||||
key => ` ${key}: ...,`
|
|
||||||
),
|
|
||||||
' },',
|
|
||||||
'}',
|
|
||||||
'\n',
|
|
||||||
'with:',
|
|
||||||
'{',
|
|
||||||
...Object.keys(screenOptions[deprecatedKey]).map(
|
|
||||||
key =>
|
|
||||||
` ${deprecatedKey + key[0].toUpperCase() + key.slice(1)}: ...,`
|
|
||||||
),
|
|
||||||
'}',
|
|
||||||
].join('\n')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,10 +0,0 @@
|
|||||||
import { NativeModules } from 'react-native';
|
|
||||||
const { PlatformConstants } = NativeModules;
|
|
||||||
|
|
||||||
export const supportsImprovedSpringAnimation = () => {
|
|
||||||
if (PlatformConstants && PlatformConstants.reactNativeVersion) {
|
|
||||||
const { major, minor } = PlatformConstants.reactNativeVersion;
|
|
||||||
return minor >= 50 || (major === 0 && minor === 0); // `master` has major + minor set to 0
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
@ -1,9 +0,0 @@
|
|||||||
const getActiveChildNavigationOptions = (navigation, screenProps) => {
|
|
||||||
const { state, router, getChildNavigation } = navigation;
|
|
||||||
const activeRoute = state.routes[state.index];
|
|
||||||
const activeNavigation = getChildNavigation(activeRoute.key);
|
|
||||||
const options = router.getScreenOptions(activeNavigation, screenProps);
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getActiveChildNavigationOptions;
|
|
@ -1,42 +0,0 @@
|
|||||||
function getSceneIndicesForInterpolationInputRange(props) {
|
|
||||||
const { scene, scenes } = props;
|
|
||||||
const index = scene.index;
|
|
||||||
const lastSceneIndexInScenes = scenes.length - 1;
|
|
||||||
const isBack = !scenes[lastSceneIndexInScenes].isActive;
|
|
||||||
|
|
||||||
if (isBack) {
|
|
||||||
const currentSceneIndexInScenes = scenes.findIndex(item => item === scene);
|
|
||||||
const targetSceneIndexInScenes = scenes.findIndex(item => item.isActive);
|
|
||||||
const targetSceneIndex = scenes[targetSceneIndexInScenes].index;
|
|
||||||
const lastSceneIndex = scenes[lastSceneIndexInScenes].index;
|
|
||||||
|
|
||||||
if (
|
|
||||||
index !== targetSceneIndex &&
|
|
||||||
currentSceneIndexInScenes === lastSceneIndexInScenes
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
first: Math.min(targetSceneIndex, index - 1),
|
|
||||||
last: index + 1,
|
|
||||||
};
|
|
||||||
} else if (
|
|
||||||
index === targetSceneIndex &&
|
|
||||||
currentSceneIndexInScenes === targetSceneIndexInScenes
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
first: index - 1,
|
|
||||||
last: Math.max(lastSceneIndex, index + 1),
|
|
||||||
};
|
|
||||||
} else if (
|
|
||||||
index === targetSceneIndex ||
|
|
||||||
currentSceneIndexInScenes > targetSceneIndexInScenes
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return { first: index - 1, last: index + 1 };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return { first: index - 1, last: index + 1 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default getSceneIndicesForInterpolationInputRange;
|
|
@ -1,60 +0,0 @@
|
|||||||
/*eslint-disable no-self-compare */
|
|
||||||
|
|
||||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* inlined Object.is polyfill to avoid requiring consumers ship their own
|
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
|
||||||
*/
|
|
||||||
function is(x, y) {
|
|
||||||
// SameValue algorithm
|
|
||||||
if (x === y) {
|
|
||||||
// Steps 1-5, 7-10
|
|
||||||
// Steps 6.b-6.e: +0 != -0
|
|
||||||
// Added the nonzero y check to make Flow happy, but it is redundant
|
|
||||||
return x !== 0 || y !== 0 || 1 / x === 1 / y;
|
|
||||||
} else {
|
|
||||||
// Step 6.a: NaN == NaN
|
|
||||||
return x !== x && y !== y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs equality by iterating through keys on an object and returning false
|
|
||||||
* when any key has values which are not strictly equal between the arguments.
|
|
||||||
* Returns true when the values of all keys are strictly equal.
|
|
||||||
*/
|
|
||||||
function shallowEqual(objA, objB) {
|
|
||||||
if (is(objA, objB)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof objA !== 'object' ||
|
|
||||||
objA === null ||
|
|
||||||
typeof objB !== 'object' ||
|
|
||||||
objB === null
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keysA = Object.keys(objA);
|
|
||||||
const keysB = Object.keys(objB);
|
|
||||||
|
|
||||||
if (keysA.length !== keysB.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for A's keys different from B.
|
|
||||||
for (let i = 0; i < keysA.length; i++) {
|
|
||||||
if (
|
|
||||||
!hasOwnProperty.call(objB, keysA[i]) ||
|
|
||||||
!is(objA[keysA[i]], objB[keysA[i]])
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default shallowEqual;
|
|
@ -1,8 +0,0 @@
|
|||||||
export default (obj, key, defaultValue) => {
|
|
||||||
if (obj.hasOwnProperty(key) && typeof obj[key] !== 'undefined') {
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
obj[key] = defaultValue;
|
|
||||||
return obj;
|
|
||||||
};
|
|
@ -1,10 +0,0 @@
|
|||||||
export default class AnimatedValueSubscription {
|
|
||||||
constructor(value, callback) {
|
|
||||||
this._value = value;
|
|
||||||
this._token = value.addListener(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
remove() {
|
|
||||||
this._value.removeListener(this._token);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
import { NavigationConsumer } from './NavigationContext';
|
|
||||||
|
|
||||||
export default NavigationConsumer;
|
|
@ -1,11 +0,0 @@
|
|||||||
import createReactContext from 'create-react-context';
|
|
||||||
|
|
||||||
const NavigationContext = createReactContext();
|
|
||||||
|
|
||||||
export const NavigationProvider = NavigationContext.Provider;
|
|
||||||
export const NavigationConsumer = NavigationContext.Consumer;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
NavigationProvider,
|
|
||||||
NavigationConsumer,
|
|
||||||
};
|
|
@ -1,57 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import withNavigation from './withNavigation';
|
|
||||||
|
|
||||||
const EventNameToPropName = {
|
|
||||||
willFocus: 'onWillFocus',
|
|
||||||
didFocus: 'onDidFocus',
|
|
||||||
willBlur: 'onWillBlur',
|
|
||||||
didBlur: 'onDidBlur',
|
|
||||||
};
|
|
||||||
|
|
||||||
const EventNames = Object.keys(EventNameToPropName);
|
|
||||||
|
|
||||||
class NavigationEvents extends React.Component {
|
|
||||||
componentDidMount() {
|
|
||||||
this.subscriptions = {};
|
|
||||||
EventNames.forEach(this.addListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
EventNames.forEach(eventName => {
|
|
||||||
const listenerHasChanged =
|
|
||||||
this.props[EventNameToPropName[eventName]] !==
|
|
||||||
prevProps[EventNameToPropName[eventName]];
|
|
||||||
if (listenerHasChanged) {
|
|
||||||
this.removeListener(eventName);
|
|
||||||
this.addListener(eventName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
EventNames.forEach(this.removeListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
addListener = eventName => {
|
|
||||||
const listener = this.props[EventNameToPropName[eventName]];
|
|
||||||
if (listener) {
|
|
||||||
this.subscriptions[eventName] = this.props.navigation.addListener(
|
|
||||||
eventName,
|
|
||||||
listener
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
removeListener = eventName => {
|
|
||||||
if (this.subscriptions[eventName]) {
|
|
||||||
this.subscriptions[eventName].remove();
|
|
||||||
this.subscriptions[eventName] = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withNavigation(NavigationEvents);
|
|
@ -1,3 +0,0 @@
|
|||||||
import { NavigationProvider } from './NavigationContext';
|
|
||||||
|
|
||||||
export default NavigationProvider;
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { Platform, StyleSheet, View } from 'react-native';
|
import { Platform, StyleSheet, View } from 'react-native';
|
||||||
import { polyfill } from 'react-lifecycles-compat';
|
import { polyfill } from 'react-lifecycles-compat';
|
||||||
|
|
||||||
import SceneView from './SceneView';
|
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
|
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
|
||||||
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { NavigationProvider } from './NavigationContext';
|
|
||||||
|
|
||||||
export default class SceneView extends React.PureComponent {
|
|
||||||
render() {
|
|
||||||
const { screenProps, component: Component, navigation } = this.props;
|
|
||||||
return (
|
|
||||||
<NavigationProvider value={navigation}>
|
|
||||||
<Component screenProps={screenProps} navigation={navigation} />
|
|
||||||
</NavigationProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import SceneView from '../SceneView';
|
|
||||||
|
|
||||||
export default class SwitchView extends React.Component {
|
|
||||||
render() {
|
|
||||||
const { state } = this.props.navigation;
|
|
||||||
const activeKey = state.routes[state.index].key;
|
|
||||||
const descriptor = this.props.descriptors[activeKey];
|
|
||||||
const ChildComponent = descriptor.getComponent();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SceneView
|
|
||||||
component={ChildComponent}
|
|
||||||
navigation={descriptor.navigation}
|
|
||||||
screenProps={this.props.screenProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchableItem renders a touchable that looks native on both iOS and Android.
|
|
||||||
*
|
|
||||||
* It provides an abstraction on top of TouchableNativeFeedback and
|
|
||||||
* TouchableOpacity.
|
|
||||||
*
|
|
||||||
* On iOS you can pass the props of TouchableOpacity, on Android pass the props
|
|
||||||
* of TouchableNativeFeedback.
|
|
||||||
*/
|
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
Platform,
|
|
||||||
TouchableNativeFeedback,
|
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
|
||||||
} from 'react-native';
|
|
||||||
|
|
||||||
const ANDROID_VERSION_LOLLIPOP = 21;
|
|
||||||
|
|
||||||
export default class TouchableItem extends React.Component {
|
|
||||||
static defaultProps = {
|
|
||||||
borderless: false,
|
|
||||||
pressColor: 'rgba(0, 0, 0, .32)',
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
/*
|
|
||||||
* TouchableNativeFeedback.Ripple causes a crash on old Android versions,
|
|
||||||
* therefore only enable it on Android Lollipop and above.
|
|
||||||
*
|
|
||||||
* All touchables on Android should have the ripple effect according to
|
|
||||||
* platform design guidelines.
|
|
||||||
* We need to pass the background prop to specify a borderless ripple effect.
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
Platform.OS === 'android' &&
|
|
||||||
Platform.Version >= ANDROID_VERSION_LOLLIPOP
|
|
||||||
) {
|
|
||||||
const { style, ...rest } = this.props;
|
|
||||||
return (
|
|
||||||
<TouchableNativeFeedback
|
|
||||||
{...rest}
|
|
||||||
style={null}
|
|
||||||
background={TouchableNativeFeedback.Ripple(
|
|
||||||
this.props.pressColor,
|
|
||||||
this.props.borderless
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<View style={style}>{React.Children.only(this.props.children)}</View>
|
|
||||||
</TouchableNativeFeedback>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TouchableOpacity {...this.props}>{this.props.children}</TouchableOpacity>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,240 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import renderer from 'react-test-renderer';
|
|
||||||
import NavigationEvents from '../NavigationEvents';
|
|
||||||
import { NavigationProvider } from '../NavigationContext';
|
|
||||||
|
|
||||||
const createListener = () => payload => {};
|
|
||||||
|
|
||||||
// An easy way to create the 4 listeners prop
|
|
||||||
const createEventListenersProp = () => ({
|
|
||||||
onWillFocus: createListener(),
|
|
||||||
onDidFocus: createListener(),
|
|
||||||
onWillBlur: createListener(),
|
|
||||||
onDidBlur: createListener(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const createNavigationAndHelpers = () => {
|
|
||||||
// A little API to spy on subscription remove calls that are performed during the tests
|
|
||||||
const removeCallsAPI = (() => {
|
|
||||||
let removeCalls = [];
|
|
||||||
return {
|
|
||||||
reset: () => {
|
|
||||||
removeCalls = [];
|
|
||||||
},
|
|
||||||
add: (name, handler) => {
|
|
||||||
removeCalls.push({ name, handler });
|
|
||||||
},
|
|
||||||
checkRemoveCalled: count => {
|
|
||||||
expect(removeCalls.length).toBe(count);
|
|
||||||
},
|
|
||||||
checkRemoveCalledWith: (name, handler) => {
|
|
||||||
expect(removeCalls).toContainEqual({ name, handler });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
const navigation = {
|
|
||||||
addListener: jest.fn((name, handler) => {
|
|
||||||
return {
|
|
||||||
remove: () => removeCallsAPI.add(name, handler),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkAddListenerCalled = count => {
|
|
||||||
expect(navigation.addListener).toHaveBeenCalledTimes(count);
|
|
||||||
};
|
|
||||||
const checkAddListenerCalledWith = (eventName, handler) => {
|
|
||||||
expect(navigation.addListener).toHaveBeenCalledWith(eventName, handler);
|
|
||||||
};
|
|
||||||
const checkRemoveCalled = count => {
|
|
||||||
removeCallsAPI.checkRemoveCalled(count);
|
|
||||||
};
|
|
||||||
const checkRemoveCalledWith = (eventName, handler) => {
|
|
||||||
removeCallsAPI.checkRemoveCalledWith(eventName, handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
navigation,
|
|
||||||
removeCallsAPI,
|
|
||||||
checkAddListenerCalled,
|
|
||||||
checkAddListenerCalledWith,
|
|
||||||
checkRemoveCalled,
|
|
||||||
checkRemoveCalledWith,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// We test 2 distinct ways to provide the navigation to the NavigationEvents (prop/context)
|
|
||||||
const NavigationEventsTestComp = ({
|
|
||||||
withContext = true,
|
|
||||||
navigation,
|
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
if (withContext) {
|
|
||||||
return (
|
|
||||||
<NavigationProvider value={navigation}>
|
|
||||||
<NavigationEvents {...props} />
|
|
||||||
</NavigationProvider>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <NavigationEvents navigation={navigation} {...props} />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('NavigationEvents', () => {
|
|
||||||
it('add all listeners with navigation prop', () => {
|
|
||||||
const {
|
|
||||||
navigation,
|
|
||||||
checkAddListenerCalled,
|
|
||||||
checkAddListenerCalledWith,
|
|
||||||
} = createNavigationAndHelpers();
|
|
||||||
const eventListenerProps = createEventListenersProp();
|
|
||||||
const component = renderer.create(
|
|
||||||
<NavigationEventsTestComp
|
|
||||||
withContext={false}
|
|
||||||
navigation={navigation}
|
|
||||||
{...eventListenerProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
checkAddListenerCalled(4);
|
|
||||||
checkAddListenerCalledWith('willBlur', eventListenerProps.onWillBlur);
|
|
||||||
checkAddListenerCalledWith('willFocus', eventListenerProps.onWillFocus);
|
|
||||||
checkAddListenerCalledWith('didBlur', eventListenerProps.onDidBlur);
|
|
||||||
checkAddListenerCalledWith('didFocus', eventListenerProps.onDidFocus);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('add all listeners with navigation context', () => {
|
|
||||||
const {
|
|
||||||
navigation,
|
|
||||||
checkAddListenerCalled,
|
|
||||||
checkAddListenerCalledWith,
|
|
||||||
} = createNavigationAndHelpers();
|
|
||||||
const eventListenerProps = createEventListenersProp();
|
|
||||||
const component = renderer.create(
|
|
||||||
<NavigationEventsTestComp
|
|
||||||
withContext={true}
|
|
||||||
navigation={navigation}
|
|
||||||
{...eventListenerProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
checkAddListenerCalled(4);
|
|
||||||
checkAddListenerCalledWith('willBlur', eventListenerProps.onWillBlur);
|
|
||||||
checkAddListenerCalledWith('willFocus', eventListenerProps.onWillFocus);
|
|
||||||
checkAddListenerCalledWith('didBlur', eventListenerProps.onDidBlur);
|
|
||||||
checkAddListenerCalledWith('didFocus', eventListenerProps.onDidFocus);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('remove all listeners on unmount', () => {
|
|
||||||
const {
|
|
||||||
navigation,
|
|
||||||
checkRemoveCalled,
|
|
||||||
checkRemoveCalledWith,
|
|
||||||
} = createNavigationAndHelpers();
|
|
||||||
const eventListenerProps = createEventListenersProp();
|
|
||||||
|
|
||||||
const component = renderer.create(
|
|
||||||
<NavigationEventsTestComp
|
|
||||||
navigation={navigation}
|
|
||||||
{...eventListenerProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
checkRemoveCalled(0);
|
|
||||||
component.unmount();
|
|
||||||
checkRemoveCalled(4);
|
|
||||||
checkRemoveCalledWith('willBlur', eventListenerProps.onWillBlur);
|
|
||||||
checkRemoveCalledWith('willFocus', eventListenerProps.onWillFocus);
|
|
||||||
checkRemoveCalledWith('didBlur', eventListenerProps.onDidBlur);
|
|
||||||
checkRemoveCalledWith('didFocus', eventListenerProps.onDidFocus);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('add a single listener', () => {
|
|
||||||
const {
|
|
||||||
navigation,
|
|
||||||
checkAddListenerCalled,
|
|
||||||
checkAddListenerCalledWith,
|
|
||||||
} = createNavigationAndHelpers();
|
|
||||||
const listener = createListener();
|
|
||||||
const component = renderer.create(
|
|
||||||
<NavigationEventsTestComp navigation={navigation} onDidFocus={listener} />
|
|
||||||
);
|
|
||||||
checkAddListenerCalled(1);
|
|
||||||
checkAddListenerCalledWith('didFocus', listener);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('do not attempt to add/remove stable listeners on update', () => {
|
|
||||||
const {
|
|
||||||
navigation,
|
|
||||||
checkAddListenerCalled,
|
|
||||||
checkAddListenerCalledWith,
|
|
||||||
} = createNavigationAndHelpers();
|
|
||||||
const eventListenerProps = createEventListenersProp();
|
|
||||||
const component = renderer.create(
|
|
||||||
<NavigationEventsTestComp
|
|
||||||
navigation={navigation}
|
|
||||||
{...eventListenerProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
component.update(
|
|
||||||
<NavigationEventsTestComp
|
|
||||||
navigation={navigation}
|
|
||||||
{...eventListenerProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
component.update(
|
|
||||||
<NavigationEventsTestComp
|
|
||||||
navigation={navigation}
|
|
||||||
{...eventListenerProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
checkAddListenerCalled(4);
|
|
||||||
checkAddListenerCalledWith('willBlur', eventListenerProps.onWillBlur);
|
|
||||||
checkAddListenerCalledWith('willFocus', eventListenerProps.onWillFocus);
|
|
||||||
checkAddListenerCalledWith('didBlur', eventListenerProps.onDidBlur);
|
|
||||||
checkAddListenerCalledWith('didFocus', eventListenerProps.onDidFocus);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('add, remove and replace (remove+add) listeners on complex updates', () => {
|
|
||||||
const {
|
|
||||||
navigation,
|
|
||||||
checkAddListenerCalled,
|
|
||||||
checkAddListenerCalledWith,
|
|
||||||
checkRemoveCalled,
|
|
||||||
checkRemoveCalledWith,
|
|
||||||
} = createNavigationAndHelpers();
|
|
||||||
const eventListenerProps = createEventListenersProp();
|
|
||||||
|
|
||||||
const component = renderer.create(
|
|
||||||
<NavigationEventsTestComp
|
|
||||||
navigation={navigation}
|
|
||||||
{...eventListenerProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
checkAddListenerCalled(4);
|
|
||||||
checkAddListenerCalledWith('willBlur', eventListenerProps.onWillBlur);
|
|
||||||
checkAddListenerCalledWith('willFocus', eventListenerProps.onWillFocus);
|
|
||||||
checkAddListenerCalledWith('didBlur', eventListenerProps.onDidBlur);
|
|
||||||
checkAddListenerCalledWith('didFocus', eventListenerProps.onDidFocus);
|
|
||||||
checkRemoveCalled(0);
|
|
||||||
|
|
||||||
const onWillFocus2 = createListener();
|
|
||||||
const onDidFocus2 = createListener();
|
|
||||||
|
|
||||||
component.update(
|
|
||||||
<NavigationEventsTestComp
|
|
||||||
navigation={navigation}
|
|
||||||
onWillBlur={eventListenerProps.onWillBlur}
|
|
||||||
onDidBlur={undefined}
|
|
||||||
onWillFocus={onWillFocus2}
|
|
||||||
onDidFocus={onDidFocus2}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
checkAddListenerCalled(6);
|
|
||||||
checkAddListenerCalledWith('willFocus', onWillFocus2);
|
|
||||||
checkAddListenerCalledWith('didFocus', onDidFocus2);
|
|
||||||
checkRemoveCalled(3);
|
|
||||||
checkRemoveCalledWith('didBlur', eventListenerProps.onDidBlur);
|
|
||||||
checkRemoveCalledWith('willFocus', eventListenerProps.onWillFocus);
|
|
||||||
checkRemoveCalledWith('didFocus', eventListenerProps.onDidFocus);
|
|
||||||
});
|
|
||||||
});
|
|
Before Width: | Height: | Size: 659 B |
Before Width: | Height: | Size: 290 B |
Before Width: | Height: | Size: 134 B |
Before Width: | Height: | Size: 373 B |
Before Width: | Height: | Size: 100 B |
Before Width: | Height: | Size: 290 B |
Before Width: | Height: | Size: 134 B |
Before Width: | Height: | Size: 405 B |
Before Width: | Height: | Size: 167 B |
Before Width: | Height: | Size: 761 B |
Before Width: | Height: | Size: 207 B |
Before Width: | Height: | Size: 812 B |
@ -1,35 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import hoistStatics from 'hoist-non-react-statics';
|
|
||||||
import invariant from '../utils/invariant';
|
|
||||||
import { NavigationConsumer } from './NavigationContext';
|
|
||||||
|
|
||||||
export default function withNavigation(Component) {
|
|
||||||
class ComponentWithNavigation extends React.Component {
|
|
||||||
static displayName = `withNavigation(${Component.displayName ||
|
|
||||||
Component.name})`;
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const navigationProp = this.props.navigation;
|
|
||||||
return (
|
|
||||||
<NavigationConsumer>
|
|
||||||
{navigationContext => {
|
|
||||||
const navigation = navigationProp || navigationContext;
|
|
||||||
invariant(
|
|
||||||
!!navigation,
|
|
||||||
'withNavigation can only be used on a view hierarchy of a navigator. The wrapped component is unable to get access to navigation from props or context.'
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Component
|
|
||||||
{...this.props}
|
|
||||||
navigation={navigation}
|
|
||||||
ref={this.props.onRef}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</NavigationConsumer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hoistStatics(ComponentWithNavigation, Component);
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import hoistStatics from 'hoist-non-react-statics';
|
|
||||||
import invariant from '../utils/invariant';
|
|
||||||
import withNavigation from './withNavigation';
|
|
||||||
|
|
||||||
export default function withNavigationFocus(Component) {
|
|
||||||
class ComponentWithNavigationFocus extends React.Component {
|
|
||||||
static displayName = `withNavigationFocus(${Component.displayName ||
|
|
||||||
Component.name})`;
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isFocused: props.navigation ? props.navigation.isFocused() : false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const { navigation } = this.props;
|
|
||||||
invariant(
|
|
||||||
!!navigation,
|
|
||||||
'withNavigationFocus can only be used on a view hierarchy of a navigator. The wrapped component is unable to get access to navigation from props or context.'
|
|
||||||
);
|
|
||||||
|
|
||||||
this.subscriptions = [
|
|
||||||
navigation.addListener('didFocus', () =>
|
|
||||||
this.setState({ isFocused: true })
|
|
||||||
),
|
|
||||||
navigation.addListener('willBlur', () =>
|
|
||||||
this.setState({ isFocused: false })
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.subscriptions.forEach(sub => sub.remove());
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Component
|
|
||||||
{...this.props}
|
|
||||||
isFocused={this.state.isFocused}
|
|
||||||
ref={this.props.onRef}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hoistStatics(withNavigation(ComponentWithNavigationFocus), Component);
|
|
||||||
}
|
|
46
yarn.lock
@ -18,6 +18,16 @@
|
|||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
js-tokens "^4.0.0"
|
js-tokens "^4.0.0"
|
||||||
|
|
||||||
|
"@react-navigation/core@^3.0.0-alpha.2":
|
||||||
|
version "3.0.0-alpha.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-3.0.0-alpha.2.tgz#a47de58078ae1178cf37aeaac2512d78a0907451"
|
||||||
|
integrity sha512-8tkJhxFJCrAujPZNPsfnkj16E2L8KTZNH89Z9hF+zNIebYGfWphLBzx/xbdNk8nC9SFstBppj1k/KJE6ilaSmA==
|
||||||
|
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"
|
||||||
|
|
||||||
abab@^2.0.0:
|
abab@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
|
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
|
||||||
@ -1463,11 +1473,6 @@ circular-json@^0.3.1:
|
|||||||
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
|
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
|
||||||
integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==
|
integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==
|
||||||
|
|
||||||
clamp@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/clamp/-/clamp-1.0.1.tgz#66a0e64011816e37196828fdc8c8c147312c8634"
|
|
||||||
integrity sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ=
|
|
||||||
|
|
||||||
class-utils@^0.3.5:
|
class-utils@^0.3.5:
|
||||||
version "0.3.6"
|
version "0.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
|
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
|
||||||
@ -1798,10 +1803,10 @@ create-react-class@^15.5.2:
|
|||||||
loose-envify "^1.3.1"
|
loose-envify "^1.3.1"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
create-react-context@0.2.2:
|
create-react-context@^0.2.3:
|
||||||
version "0.2.2"
|
version "0.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.2.tgz#9836542f9aaa22868cd7d4a6f82667df38019dca"
|
resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.3.tgz#9ec140a6914a22ef04b8b09b7771de89567cb6f3"
|
||||||
integrity sha512-KkpaLARMhsTsgp0d2NA/R94F/eDLbhXERdIq3LvX2biCAXcDvHYoOqHfWCHf1+OLj+HKBotLG3KqaOOf+C1C+A==
|
integrity sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==
|
||||||
dependencies:
|
dependencies:
|
||||||
fbjs "^0.8.0"
|
fbjs "^0.8.0"
|
||||||
gud "^1.0.0"
|
gud "^1.0.0"
|
||||||
@ -3029,11 +3034,18 @@ hoek@2.x.x:
|
|||||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
|
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
|
||||||
integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=
|
integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=
|
||||||
|
|
||||||
hoist-non-react-statics@^2.2.0, hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
|
hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
|
||||||
version "2.5.5"
|
version "2.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
||||||
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
|
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
|
||||||
|
|
||||||
|
hoist-non-react-statics@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.0.1.tgz#fba3e7df0210eb9447757ca1a7cb607162f0a364"
|
||||||
|
integrity sha512-1kXwPsOi0OGQIZNVMPvgWJ9tSnGMiMfJdihqEzrPEXlHOBh9AAHXX/QYmAJTXztnz/K+PQ8ryCb4eGaN6HlGbQ==
|
||||||
|
dependencies:
|
||||||
|
react-is "^16.3.2"
|
||||||
|
|
||||||
home-or-tmp@^2.0.0:
|
home-or-tmp@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
|
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
|
||||||
@ -5097,12 +5109,10 @@ path-parse@^1.0.5:
|
|||||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
||||||
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
||||||
|
|
||||||
path-to-regexp@^1.7.0:
|
path-to-regexp@^2.4.0:
|
||||||
version "1.7.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704"
|
||||||
integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=
|
integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==
|
||||||
dependencies:
|
|
||||||
isarray "0.0.1"
|
|
||||||
|
|
||||||
path-type@^1.0.0:
|
path-type@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
@ -5344,7 +5354,7 @@ qs@~6.5.2:
|
|||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||||
|
|
||||||
query-string@^6.1.0:
|
query-string@^6.2.0:
|
||||||
version "6.2.0"
|
version "6.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.2.0.tgz#468edeb542b7e0538f9f9b1aeb26f034f19c86e1"
|
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.2.0.tgz#468edeb542b7e0538f9f9b1aeb26f034f19c86e1"
|
||||||
integrity sha512-5wupExkIt8RYL4h/FE+WTg3JHk62e6fFPWtAZA9J5IWK1PfTfKkMS93HBUHcFpeYi9KsY5pFbh+ldvEyaz5MyA==
|
integrity sha512-5wupExkIt8RYL4h/FE+WTg3JHk62e6fFPWtAZA9J5IWK1PfTfKkMS93HBUHcFpeYi9KsY5pFbh+ldvEyaz5MyA==
|
||||||
@ -5408,7 +5418,7 @@ react-devtools-core@3.0.0:
|
|||||||
shell-quote "^1.6.1"
|
shell-quote "^1.6.1"
|
||||||
ws "^2.0.3"
|
ws "^2.0.3"
|
||||||
|
|
||||||
react-is@^16.5.2:
|
react-is@^16.3.2, react-is@^16.5.2:
|
||||||
version "16.5.2"
|
version "16.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
|
||||||
integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==
|
integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==
|
||||||
|