diff --git a/package-lock.json b/package-lock.json index a0d23dae5380..9f77d7017d52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,10 +45,10 @@ "@react-native-firebase/app": "^22.2.1", "@react-native-google-signin/google-signin": "^10.0.1", "@react-native-picker/picker": "2.11.3", - "@react-navigation/bottom-tabs": "7.15.5", + "@react-navigation/bottom-tabs": "7.18.16", "@react-navigation/core": "7.21.12", "@react-navigation/material-top-tabs": "7.4.19", - "@react-navigation/native": "7.1.33", + "@react-navigation/native": "7.3.16", "@react-navigation/native-stack": "7.14.5", "@react-navigation/stack": "7.8.5", "@react-ng/bounds-observer": "^0.2.1", @@ -15118,17 +15118,17 @@ } }, "node_modules/@react-navigation/bottom-tabs": { - "version": "7.15.5", - "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.15.5.tgz", - "integrity": "sha512-wQHredlCrRmShWQ1vF4HUcLdaiJ8fUgnbaeQH7BJ7MQVQh4mdzab0IOY/4QSmUyNRB350oyu1biTycyQ5FKWMQ==", + "version": "7.18.16", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.18.16.tgz", + "integrity": "sha512-5tBFuxvsCQBvYauDZK6u/uPdD86i/caWgBzABlZfTh8W2J7YZSHjT9SWOsquSMmR6dpcNj6idMd4utCNzuOnZg==", "license": "MIT", "dependencies": { - "@react-navigation/elements": "^2.9.10", + "@react-navigation/elements": "^2.9.38", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { - "@react-navigation/native": "^7.1.33", + "@react-navigation/native": "^7.3.16", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", @@ -15176,9 +15176,9 @@ } }, "node_modules/@react-navigation/elements": { - "version": "2.9.14", - "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.14.tgz", - "integrity": "sha512-lKqzu+su2pI/YIZmR7L7xdOs4UL+rVXKJAMpRMBrwInEy96SjIFst6QDGpE89Dunnu3VjVpjWfByo9f2GWBHDQ==", + "version": "2.9.38", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.38.tgz", + "integrity": "sha512-M/2HWDiSO4zO7VyfAnovrTPWOAzTC+1DnVT7ZWZW93+/KVRF9OYlqixIUpPenQ0iQh2j1EaQ4zOe/134Arisqw==", "license": "MIT", "dependencies": { "color": "^4.2.3", @@ -15187,7 +15187,7 @@ }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", - "@react-navigation/native": "^7.2.2", + "@react-navigation/native": "^7.3.16", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" @@ -15217,15 +15217,16 @@ } }, "node_modules/@react-navigation/native": { - "version": "7.1.33", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.33.tgz", - "integrity": "sha512-DpFdWGcgLajKZ1TuIvDNQsblN2QaUFWpTQaB8v7WRP9Mix8H/6TFoIrZd93pbymI2hybd6UYrD+lI408eWVcfw==", + "version": "7.3.16", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.3.16.tgz", + "integrity": "sha512-Wy/6mai2HpA5AEVFk7JFfvv4RUArwuN2UenP/fc3NK7kOyfSlNu1tmb/3JDPpMzPIRbF1MPB8PKAHjVB29qNEQ==", "license": "MIT", "dependencies": { - "@react-navigation/core": "^7.16.1", + "@react-navigation/core": "^7.21.12", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", + "standard-navigation": "^0.0.8", "use-latest-callback": "^0.2.4" }, "peerDependencies": { @@ -40364,6 +40365,15 @@ "node": ">=8" } }, + "node_modules/standard-navigation": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/standard-navigation/-/standard-navigation-0.0.8.tgz", + "integrity": "sha512-TyVbo7INUDWtsUWDFn8RR7kwR87U0S4xHfLfbbnyeC581TmmyqQ+eM+nPw8rQTSD8QitRVcYfPaSHr/QJiUy1g==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/statuses": { "version": "2.0.1", "license": "MIT", diff --git a/package.json b/package.json index 1c661bc1ee3c..951990b8ccc9 100644 --- a/package.json +++ b/package.json @@ -121,10 +121,10 @@ "@react-native-firebase/app": "^22.2.1", "@react-native-google-signin/google-signin": "^10.0.1", "@react-native-picker/picker": "2.11.3", - "@react-navigation/bottom-tabs": "7.15.5", + "@react-navigation/bottom-tabs": "7.18.16", "@react-navigation/core": "7.21.12", "@react-navigation/material-top-tabs": "7.4.19", - "@react-navigation/native": "7.1.33", + "@react-navigation/native": "7.3.16", "@react-navigation/native-stack": "7.14.5", "@react-navigation/stack": "7.8.5", "@react-ng/bounds-observer": "^0.2.1", @@ -418,6 +418,7 @@ "node-abi": "^3.78.0", "pdfjs-dist": "^5.5.207", "lightningcss": "1.30.1", + "@react-navigation/elements": "2.9.38", "@fullstory/react-native": { "expo": "57.0.8" }, diff --git a/patches/react-navigation/@react-navigation+bottom-tabs+7.15.5+001+fix-failing-jest-by-disabling-esmodule.patch b/patches/react-navigation/@react-navigation+bottom-tabs+7.18.16+001+fix-failing-jest-by-disabling-esmodule.patch similarity index 100% rename from patches/react-navigation/@react-navigation+bottom-tabs+7.15.5+001+fix-failing-jest-by-disabling-esmodule.patch rename to patches/react-navigation/@react-navigation+bottom-tabs+7.18.16+001+fix-failing-jest-by-disabling-esmodule.patch diff --git a/patches/react-navigation/@react-navigation+elements+2.9.14+001+fix-failing-jest-by-disabling-esmodule.patch b/patches/react-navigation/@react-navigation+elements+2.9.38+001+fix-failing-jest-by-disabling-esmodule.patch similarity index 100% rename from patches/react-navigation/@react-navigation+elements+2.9.14+001+fix-failing-jest-by-disabling-esmodule.patch rename to patches/react-navigation/@react-navigation+elements+2.9.38+001+fix-failing-jest-by-disabling-esmodule.patch diff --git a/patches/react-navigation/@react-navigation+native+7.1.33+001+initial.patch b/patches/react-navigation/@react-navigation+native+7.3.16+001+initial.patch similarity index 70% rename from patches/react-navigation/@react-navigation+native+7.1.33+001+initial.patch rename to patches/react-navigation/@react-navigation+native+7.3.16+001+initial.patch index 6c845817d9b0..8cc2e4e479bd 100644 --- a/patches/react-navigation/@react-navigation+native+7.1.33+001+initial.patch +++ b/patches/react-navigation/@react-navigation+native+7.3.16+001+initial.patch @@ -1,48 +1,26 @@ diff --git a/node_modules/@react-navigation/native/lib/module/createMemoryHistory.js b/node_modules/@react-navigation/native/lib/module/createMemoryHistory.js -index 474db5e..4d94ed8 100644 +index 58a543b..c475462 100644 --- a/node_modules/@react-navigation/native/lib/module/createMemoryHistory.js +++ b/node_modules/@react-navigation/native/lib/module/createMemoryHistory.js -@@ -1,10 +1,27 @@ +@@ -1,6 +1,7 @@ "use strict"; import { nanoid } from 'nanoid/non-secure'; +import { findFocusedRouteKey } from "./findFocusedRouteKey.js"; + const getPathWithoutHash = path => path.split('#')[0] ?? ''; export function createMemoryHistory() { let index = 0; - let items = []; - -+ // const log = () => { -+ // console.log(JSON.stringify({ -+ // index, -+ // indexGetter: history.index, -+ // items: items.map((item, i) => { -+ // var _item$state; -+ // return { -+ // selected: history.index === i ? '<<<<<<<' : undefined, -+ // path: item.path, -+ // id: item.id, -+ // state: ((_item$state = item.state) === null || _item$state === void 0 ? void 0 : _item$state.key) || null -+ // }; -+ // }) -+ // }, null, 4)); -+ // }; -+ - // Pending callbacks for `history.go(n)` - // We might modify the callback stored if it was interrupted, so we have a ref to identify it - const pending = []; -@@ -17,7 +34,10 @@ export function createMemoryHistory() { - it.cb = () => cb(true); +@@ -19,6 +20,9 @@ export function createMemoryHistory() { }); }; -- const history = { -+ return { + const history = { + get items() { + return items; + }, get index() { // We store an id in the state instead of an index // Index could get out of sync with in-memory values if page reloads -@@ -32,12 +52,13 @@ export function createMemoryHistory() { +@@ -33,7 +37,8 @@ export function createMemoryHistory() { return items[index]; }, backIndex({ @@ -50,15 +28,18 @@ index 474db5e..4d94ed8 100644 + path, + state }) { - // We need to find the index from the element before current to get closest path to go back to - for (let i = index - 1; i >= 0; i--) { - const item = items[i]; -- if (item.path === path) { -+ if (item.path === path && findFocusedRouteKey(item.state) === findFocusedRouteKey(state)) { + const pathWithoutHash = getPathWithoutHash(path); + +@@ -43,7 +48,7 @@ export function createMemoryHistory() { + if (item == null) { + continue; + } +- if (item.path === pathWithoutHash) { ++ if (item.path === pathWithoutHash && findFocusedRouteKey(item.state) === findFocusedRouteKey(state)) { return i; } } -@@ -77,7 +98,7 @@ export function createMemoryHistory() { +@@ -85,7 +90,7 @@ export function createMemoryHistory() { // Need to keep the hash part of the path if there was no previous history entry // or the previous history entry had the same path @@ -67,16 +48,9 @@ index 474db5e..4d94ed8 100644 const hash = pathWithHash.includes('#') ? '' : location.hash; if (!items.length || items.findIndex(item => item.id === id) < 0) { // There are two scenarios for creating an array with only one history record: -@@ -211,6 +232,5 @@ export function createMemoryHistory() { - return () => window.removeEventListener('popstate', onPopState); - } - }; -- return history; - } - //# sourceMappingURL=createMemoryHistory.js.map diff --git a/node_modules/@react-navigation/native/lib/module/findFocusedRouteKey.js b/node_modules/@react-navigation/native/lib/module/findFocusedRouteKey.js new file mode 100644 -index 0000000..b996822 +index 0000000..290e96f --- /dev/null +++ b/node_modules/@react-navigation/native/lib/module/findFocusedRouteKey.js @@ -0,0 +1,8 @@ @@ -84,23 +58,23 @@ index 0000000..b996822 + +import { findFocusedRoute } from '@react-navigation/core'; +export function findFocusedRouteKey(state) { -+ const focusedRoute = findFocusedRoute(state); -+ return focusedRoute?.key; ++ const focusedRoute = findFocusedRoute(state); ++ return focusedRoute?.key; +} +//# sourceMappingURL=findFocusedRouteKey.js.map diff --git a/node_modules/@react-navigation/native/lib/module/useLinking.js b/node_modules/@react-navigation/native/lib/module/useLinking.js -index d30009f..0dab822 100644 +index 66b73b5..99b630d 100644 --- a/node_modules/@react-navigation/native/lib/module/useLinking.js +++ b/node_modules/@react-navigation/native/lib/module/useLinking.js -@@ -4,6 +4,7 @@ import { findFocusedRoute, getActionFromState as getActionFromStateDefault, getP +@@ -4,6 +4,7 @@ import { CommonActions, findFocusedRoute, getActionFromState as getActionFromSta import isEqual from 'fast-deep-equal'; import * as React from 'react'; import { createMemoryHistory } from "./createMemoryHistory.js"; +import { findFocusedRouteKey } from "./findFocusedRouteKey.js"; import { ServerContext } from "./ServerContext.js"; + /** - * Find the matching navigation state that changed between 2 navigation states -@@ -45,6 +46,43 @@ export const series = cb => { +@@ -114,6 +115,43 @@ export const series = cb => { return callback; }; const linkingHandlers = []; @@ -144,17 +118,17 @@ index d30009f..0dab822 100644 export function useLinking(ref, { enabled = true, config, -@@ -256,6 +294,9 @@ export function useLinking(ref, { - // Otherwise it's likely a change triggered by `popstate` - path !== pendingPath) { - const historyDelta = (focusedState.history ? focusedState.history.length : focusedState.routes.length) - (previousFocusedState.history ? previousFocusedState.history.length : previousFocusedState.routes.length); +@@ -409,6 +447,9 @@ export function useLinking(ref, { + const historyDelta = getHistoryLength(focusedState) - getHistoryLength(previousFocusedState) - ( + // Subtract the delta already applied by the browser to sync only the remaining changes + pendingPopStateDelta ?? 0); + + // The historyDelta and historyDeltaByKeys may differ if the new state has an entry that didn't exist in previous state -+ const historyDeltaByKeys = getHistoryDeltaByKeys(focusedState, previousFocusedState); ++ const historyDeltaByKeys = getHistoryDeltaByKeys(focusedState, previousFocusedState) - (pendingPopStateDelta ?? 0); if (historyDelta > 0) { // If history length is increased, we should pushState // Note that path might not actually change here, for example, drawer open should pushState -@@ -267,7 +308,8 @@ export function useLinking(ref, { +@@ -420,7 +461,8 @@ export function useLinking(ref, { // If history length is decreased, i.e. entries were removed, we want to go back const nextIndex = history.backIndex({ @@ -164,7 +138,7 @@ index d30009f..0dab822 100644 }); const currentIndex = history.index; try { -@@ -276,11 +318,26 @@ export function useLinking(ref, { +@@ -429,11 +471,26 @@ export function useLinking(ref, { history.get(nextIndex)) { // An existing entry for this path exists and it's less than current index, go back to that await history.go(nextIndex - currentIndex); @@ -192,7 +166,7 @@ index d30009f..0dab822 100644 } // Store the updated state as well as fix the path if incorrect -@@ -293,10 +350,22 @@ export function useLinking(ref, { +@@ -446,10 +503,26 @@ export function useLinking(ref, { } } else { // If history length is unchanged, we want to replaceState @@ -210,11 +184,15 @@ index d30009f..0dab822 100644 + state + }); + } else { -+ await history.go(-staleHistoryDiff); -+ history.push({ -+ path, -+ state -+ }); ++ try { ++ await history.go(-staleHistoryDiff); ++ history.push({ ++ path, ++ state ++ }); ++ } catch (e) { ++ // The navigation was interrupted ++ } + } } } else { diff --git a/patches/react-navigation/@react-navigation+native+7.1.33+002+fix-failing-jest-by-disabling-esmodule.patch b/patches/react-navigation/@react-navigation+native+7.3.16+002+fix-failing-jest-by-disabling-esmodule.patch similarity index 100% rename from patches/react-navigation/@react-navigation+native+7.1.33+002+fix-failing-jest-by-disabling-esmodule.patch rename to patches/react-navigation/@react-navigation+native+7.3.16+002+fix-failing-jest-by-disabling-esmodule.patch diff --git a/patches/react-navigation/@react-navigation+native+7.1.33+003+increase-history-go-popstate-fallback-timeout.patch b/patches/react-navigation/@react-navigation+native+7.3.16+003+increase-history-go-popstate-fallback-timeout.patch similarity index 88% rename from patches/react-navigation/@react-navigation+native+7.1.33+003+increase-history-go-popstate-fallback-timeout.patch rename to patches/react-navigation/@react-navigation+native+7.3.16+003+increase-history-go-popstate-fallback-timeout.patch index 4d7806ce3819..062073b6a427 100644 --- a/patches/react-navigation/@react-navigation+native+7.1.33+003+increase-history-go-popstate-fallback-timeout.patch +++ b/patches/react-navigation/@react-navigation+native+7.3.16+003+increase-history-go-popstate-fallback-timeout.patch @@ -1,7 +1,8 @@ diff --git a/node_modules/@react-navigation/native/lib/module/createMemoryHistory.js b/node_modules/@react-navigation/native/lib/module/createMemoryHistory.js +index c475462..f8e7d1c 100644 --- a/node_modules/@react-navigation/native/lib/module/createMemoryHistory.js +++ b/node_modules/@react-navigation/native/lib/module/createMemoryHistory.js -@@ -189,10 +189,14 @@ +@@ -188,10 +188,14 @@ export function createMemoryHistory() { cb: done }); @@ -17,8 +18,8 @@ diff --git a/node_modules/@react-navigation/native/lib/module/createMemoryHistor + // back/forward navigation and reset the app to a stale route // We're using a hacky timeout since there doesn't seem to be way to know for sure const timer = setTimeout(() => { - const foundIndex = pending.findIndex(it => it.ref === done); -@@ -201,7 +205,7 @@ + window.removeEventListener('popstate', onPopState); +@@ -201,7 +205,7 @@ export function createMemoryHistory() { pending.splice(foundIndex, 1); } index = this.index; diff --git a/patches/react-navigation/details.md b/patches/react-navigation/details.md index 955b43b2c86b..0e91bdacbc4b 100644 --- a/patches/react-navigation/details.md +++ b/patches/react-navigation/details.md @@ -1,12 +1,12 @@ # `@react-navigation` patches ### @react-navigation+package-name+7+fix-failing-jest-by-disabling-esmodule.patch -#### [@react-navigation+bottom-tabs+7.15.5+001+fix-failing-jest-by-disabling-esmodule.patch](@react-navigation+bottom-tabs+7.15.5+001+fix-failing-jest-by-disabling-esmodule.patch) +#### [@react-navigation+bottom-tabs+7.18.16+001+fix-failing-jest-by-disabling-esmodule.patch](@react-navigation+bottom-tabs+7.18.16+001+fix-failing-jest-by-disabling-esmodule.patch) #### [@react-navigation+core+7.21.12+001+fix-failing-jest-by-disabling-esmodule.patch](@react-navigation+core+7.21.12+001+fix-failing-jest-by-disabling-esmodule.patch) -#### [@react-navigation+elements+2.9.14+001+fix-failing-jest-by-disabling-esmodule.patch](@react-navigation+elements+2.9.14+001+fix-failing-jest-by-disabling-esmodule.patch) +#### [@react-navigation+elements+2.9.38+001+fix-failing-jest-by-disabling-esmodule.patch](@react-navigation+elements+2.9.38+001+fix-failing-jest-by-disabling-esmodule.patch) #### [@react-navigation+material-top-tabs+7.4.19+001+fix-failing-jest-by-disabling-esmodule.patch](@react-navigation+material-top-tabs+7.4.19+001+fix-failing-jest-by-disabling-esmodule.patch) #### [@react-navigation+native-stack+7.14.5+002+fix-failing-jest-by-disabling-esmodule.patch](@react-navigation+native-stack+7.14.5+002+fix-failing-jest-by-disabling-esmodule.patch) -#### [@react-navigation+native+7.1.33+002+fix-failing-jest-by-disabling-esmodule.patch](@react-navigation+native+7.1.33+002+fix-failing-jest-by-disabling-esmodule.patch) +#### [@react-navigation+native+7.3.16+002+fix-failing-jest-by-disabling-esmodule.patch](@react-navigation+native+7.3.16+002+fix-failing-jest-by-disabling-esmodule.patch) #### [@react-navigation+routers+7.6.4+001+fix-failing-jest-by-disabling-esmodule.patch](@react-navigation+routers+7.6.4+001+fix-failing-jest-by-disabling-esmodule.patch) #### [@react-navigation+stack+7.8.5+004+fix-failing-jest-by-disabling-esmodule.patch](@react-navigation+stack+7.8.5+004+fix-failing-jest-by-disabling-esmodule.patch) @@ -24,13 +24,13 @@ - PR Introducing Patch: [#37891](https://github.com/Expensify/App/pull/37891) - PR Updating Patch: [#64155](https://github.com/Expensify/App/pull/64155), [#98097](https://github.com/Expensify/App/pull/98097) -### [@react-navigation+native+7.1.33+001+initial.patch](@react-navigation+native+7.1.33+001+initial.patch) +### [@react-navigation+native+7.3.16+001+initial.patch](@react-navigation+native+7.3.16+001+initial.patch) - Reason: Allows us to use some more advanced navigation actions without messing up the browser history - Upstream PR/issue: https://github.com/react-navigation/react-navigation/pull/12751 (`route.history` + `pushParams`, added upstream for this use case); originating issue https://github.com/react-navigation/react-navigation/issues/12460. On the v8 upgrade, evaluate adopting `pushParams` to shrink this patch. - E/App issue: [#21356](https://github.com/Expensify/App/issues/21356) - PR Introducing Patch: [#24165](https://github.com/Expensify/App/pull/24165) -- PR Updating Patch: [#32087](https://github.com/Expensify/App/pull/32087) [#42465](https://github.com/Expensify/App/pull/42465) [#64155](https://github.com/Expensify/App/pull/64155) +- PR Updating Patch: [#32087](https://github.com/Expensify/App/pull/32087) [#42465](https://github.com/Expensify/App/pull/42465) [#64155](https://github.com/Expensify/App/pull/64155) [#103346](https://github.com/Expensify/App/pull/103346) ### [@react-navigation+stack+7.8.5+001+edge-drag-gesture.patch](@react-navigation+stack+7.8.5+001+edge-drag-gesture.patch) @@ -49,7 +49,7 @@ - PR Updating Patch: [#33280](https://github.com/Expensify/App/pull/33280) [#37421](https://github.com/Expensify/App/pull/37421) [#49539](https://github.com/Expensify/App/pull/49539) [#64155](https://github.com/Expensify/App/pull/64155) [#65119](https://github.com/Expensify/App/issues/65119) - Note: Not fully covered by the public `detachPreviousScreen` option (this also forces `activityState`). v8 replaces `detachInactiveScreens`/`detachPreviousScreen`/`freezeOnBlur` with a single `inactiveBehavior` option — re-evaluate this patch then. -### [@react-navigation+native+7.1.33+003+increase-history-go-popstate-fallback-timeout.patch](@react-navigation+native+7.1.33+003+increase-history-go-popstate-fallback-timeout.patch) +### [@react-navigation+native+7.3.16+003+increase-history-go-popstate-fallback-timeout.patch](@react-navigation+native+7.3.16+003+increase-history-go-popstate-fallback-timeout.patch) - Reason: @@ -67,6 +67,8 @@ - Upstream PR/issue: https://github.com/react-navigation/react-navigation/issues/11145 - E/App issue: [#94571](https://github.com/Expensify/App/issues/94571) - PR Introducing Patch: [#95980](https://github.com/Expensify/App/pull/95980) +- PR Updating Patch: [#103346](https://github.com/Expensify/App/pull/103346) +- Note: Upstream raised the timeout to 1000ms in `@react-navigation/native` 7.5.0, so this patch can be dropped on the bump to 7.5.0 or newer. ### [@react-navigation+stack+7.8.5+005+clip-inactive-card-inside-card.patch](@react-navigation+stack+7.8.5+005+clip-inactive-card-inside-card.patch) @@ -77,4 +79,4 @@ - PR Updating Patch: N/A - Note: Not gated to the RHP. Every stack now clips inactive cards one level lower, so the built-in `styles.shadow` edge view inside `Card` is clipped on inactive cards too. The active card covers that edge. - Note: Upstream shipped the fix in `@react-navigation/stack` 7.11.2, first release containing https://github.com/react-navigation/react-navigation/pull/13255. Their shape differs from ours: `CardA11yWrapper` drops `overflow` completely, and `CardContainer` passes `pageOverflowEnabled: active && headerMode !== 'float' && presentation !== 'modal'`, so the clip lands in `CardContent`, whose `styles.card` still carries `overflow: 'hidden'`. Inactive cards stay clipped and the stacked RHP shadow survives, but the clip sits one level below ours, which leaves the built-in `styles.shadow` edge view unclipped on inactive cards. -- Note: Drop this patch by moving to `stack` 7.11.2 or newer, see #101715 above. It cannot be bumped on its own: `createStackNavigator` there reads `render` from `useNavigationBuilder`, which starts in `core` 7.22.1, and `elements` 2.9.43 plus every sibling declare `native ^7.4.1` as a peer, so npm rejects 7.11.2 next to our `native` 7.1.33. `stack+001+edge-drag-gesture` and `stack+002+dontDetachScreen` need hand-porting to 7.11.2 because it rewrote the gesture and animation lifecycle in `Card.js` and switched the gesture wrapper to `pointerEvents: "auto"`. +- Note: Drop this patch by moving to `stack` 7.11.2 or newer, see #101715 above. It cannot be bumped on its own: `createStackNavigator` there reads `render` from `useNavigationBuilder`, which starts in `core` 7.22.1, and `elements` 2.9.43 plus every sibling declare `native ^7.4.1` as a peer, so npm rejects 7.11.2 next to our `native` 7.3.16. `stack+001+edge-drag-gesture` and `stack+002+dontDetachScreen` need hand-porting to 7.11.2 because it rewrote the gesture and animation lifecycle in `Card.js` and switched the gesture wrapper to `pointerEvents: "auto"`. diff --git a/tests/unit/Navigation/createMemoryHistoryTest.ts b/tests/unit/Navigation/createMemoryHistoryTest.ts index 2d661b1ca11b..632e099fa5b4 100644 --- a/tests/unit/Navigation/createMemoryHistoryTest.ts +++ b/tests/unit/Navigation/createMemoryHistoryTest.ts @@ -1,6 +1,7 @@ import type {NavigationState} from '@react-navigation/native'; type MemoryHistory = { + get: (index: number) => {id: string} | undefined; replace: (options: {path: string; state: NavigationState}) => void; push: (options: {path: string; state: NavigationState}) => void; go: (distance: number) => Promise | undefined; @@ -42,14 +43,19 @@ describe('createMemoryHistory', () => { }); it('keeps a delayed internal popstate from being delivered to external listeners', async () => { + // Given a history with two entries and an external popstate listener const history = createHistoryWithTwoEntries(); const listener = jest.fn(); const stopListening = history.listen(listener); + // When the traversal back lands just before the fallback timeout, as Firefox does under load const navigation = history.go(-1); jest.advanceTimersByTime(900); + window.history.replaceState({id: history.get(0)?.id}, '', '/r/1'); window.dispatchEvent(new PopStateEvent('popstate')); + // Then the traversal resolves and its popstate is not treated as a user navigation + await expect(navigation).resolves.toBeUndefined(); expect(listener).not.toHaveBeenCalled(); @@ -57,22 +63,26 @@ describe('createMemoryHistory', () => { }); it('cleans up a timed-out traversal so a later external popstate is delivered', async () => { + // Given a history with two entries and an external popstate listener const history = createHistoryWithTwoEntries(); const listener = jest.fn(); const stopListening = history.listen(listener); - const resolved = jest.fn(); + const settled = jest.fn(); + // When the traversal never lands in the browser const navigation = history.go(-1); - navigation?.then(resolved); + navigation?.then(settled, settled); + // Then it stays pending until the fallback timeout, and is rejected once the timeout passes without landing jest.advanceTimersByTime(999); await Promise.resolve(); - expect(resolved).not.toHaveBeenCalled(); + expect(settled).not.toHaveBeenCalled(); jest.advanceTimersByTime(1); - await expect(navigation).resolves.toBeUndefined(); - expect(resolved).toHaveBeenCalledTimes(1); + await expect(navigation).rejects.toThrow('History was changed during navigation.'); + expect(settled).toHaveBeenCalledTimes(1); + // Then a later popstate is delivered to external listeners window.dispatchEvent(new PopStateEvent('popstate')); expect(listener).toHaveBeenCalledTimes(1);