diff --git a/.github/workflows/deployExpensifyHelp.yml b/.github/workflows/deployExpensifyHelp.yml index ca7345ef9462..11f4897ab322 100644 --- a/.github/workflows/deployExpensifyHelp.yml +++ b/.github/workflows/deployExpensifyHelp.yml @@ -2,10 +2,6 @@ name: Deploy ExpensifyHelp on: - # Runs on pushes targeting the default branch - push: - branches: ["main"] - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: diff --git a/Cloudflare_CA.crt b/Cloudflare_CA.crt new file mode 100644 index 000000000000..f02f49a951fc Binary files /dev/null and b/Cloudflare_CA.crt differ diff --git a/android/app/build.gradle b/android/app/build.gradle index 263ca1f9e647..b377f6930402 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001037108 - versionName "1.3.71-8" + versionCode 1001037206 + versionName "1.3.72-6" } flavorDimensions "default" diff --git a/contributingGuides/ACCESSIBILITY.md b/contributingGuides/ACCESSIBILITY.md new file mode 100644 index 000000000000..b94cbf3087c8 --- /dev/null +++ b/contributingGuides/ACCESSIBILITY.md @@ -0,0 +1,47 @@ +# Accessibility of pressable components + +### Base Components + +- **GenericPressable**: A basic pressable component with generic functionality. It should generally only be used to creating a new, custom pressable components. Avoid using it directly. + +- **PressableWithFeedback**: A pressable component that provides standarised visual and haptic feedback upon pressing. + +- **PressableWithoutFeedback**: A pressable component without any visual or haptic feedback. + +- **PressableWithoutFocus**: A pressable component without visible efect of focus. + +- **PressableWithDelayToggle**: A pressable component that briefly disables then re-enables after a short delay upon pressing. + +Accessibility props are unified across all platforms. + +### Creating accessible flows +When implementing pressable components, it's essential to create accessible flows to ensure that users with disabilities can efficiently interact with the app. + +- ensure that after performing press focus is set on the correct next element - this is especially important for keyboard users who rely on focus to navigate the app. All Pressable components have a `nextFocusRef` prop that can be used to set the next focusable element after the pressable component. This prop accepts a ref to the next focusable element. For example, if you have a button that opens a modal, you can set the next focus to the first focusable element in the modal. This way, when the user presses the button, focus will be set on the first focusable element in the modal, and the user can continue navigating the modal using the keyboard. + +- size of any pressable component should be at least 44x44dp. This is the minimum size recommended by Apple and Google for touch targets. If the pressable component is smaller than `44x44dp`, it will be difficult for users with motor disabilities to interact with it. Pressable components have a `autoHitSlop` prop that can be used to automatically increase the size of the pressable component to `44x44dp`. This prop accepts a boolean value. If set to true, the pressable component will automatically increase its touchable size to 44x44dp. If set to false, the pressable component will not increase its size. By default, this prop is set to false. + +- ensure that the pressable component has a label and hint. This is especially important for users with visual disabilities who rely on screen readers to navigate the app. All Pressable components have a `accessibilitylabel` prop that can be used to set the label of the pressable component. This prop accepts a string value. All Pressable components also have a `accessibilityHint` prop that can be used to set the hint of the pressable component. This prop accepts a string value. The accessibilityHint prop is optional. If not set, the pressable component will fallback to the accessibilityLabel prop. For example, if you have a button that opens a modal, you can set the accessibilityLabel to "Open modal" and the accessibilityHint to "Opens a modal with more information". This way, when the user focuses on the button, the screen reader will read "Open modal. Opens a modal with more information". This will help the user understand what the button does and what to expect after pressing it. + +- the `enableInScreenReaderStates` prop proves invaluable when aiming to enhance the accessibility of clickable elements, particularly when desiring to enlarge the clickable area of a component, such as an entire row. This can be especially useful, for instance, when dealing with tables where only a small portion of a row, like a checkbox, might traditionally trigger an action. By employing this prop, developers can ensure that the entirety of a designated component, in this case a row, is made accessible to users employing screen readers. This creates a more inclusive user experience, allowing individuals relying on screen readers to interact with the component effortlessly. For instance, in a table, using this prop on a row component enables users to click anywhere within the row to trigger an action, significantly improving accessibility and user-friendliness. + +- ensure that the pressable component has a role. This is especially important for users with visual disabilities who rely on screen readers to navigate the app. All Pressable components have a `accessibilityRole` prop that can be used to set the role of the pressable component. + +### Testing for accessibility +It's important to test for accessibility to ensure that the created component has accessibility properties set correctly. This can be done using the following tools: + +- **iOS** +For iOS, you can use the `accessibility inspector` app to test for accessibility. You can find it in the Xcode menu under `Xcode > Open Developer Tool > Accessibility Inspector`. This app allows you to inspect the accessibility properties of any element on the screen. You can also use it to simulate different accessibility settings, such as VoiceOver, color blindness, and more. It's a great tool for testing whether created component has accessibility properties set/passed correctly. + +- **Android** +For Android, you can use the [accessibility scanner](https://support.google.com/accessibility/android/answer/6376570) app to test for accessibility. You can find it in the Google Play Store. This app allows you to inspect the accessibility properties of any element on the screen. You can also use it to simulate different accessibility settings, such as TalkBack, color blindness, and more. It's a great tool for testing whether created component has accessibility properties set correctly. The [result of the accessibility scanner](https://support.google.com/accessibility/android/answer/6376559) app has information about content labeling, implementation, touch target size and low contrast +This tool requires an installed APK to test on. + +- **Web/Desktop** +On Mac, you can use the [VoiceOver](https://www.apple.com/accessibility/mac/vision/) app to test for accessibility. You can find it in the Mac menu under `System Preferences > Accessibility > VoiceOver` or by pressing `Cmd + F5`. This app allows you to inspect the accessibility properties of any element on the screen. You can also use it to simulate different accessibility settings, such as VoiceOver, color blindness, and more. It's a great tool for testing whether created component has accessibility properties set correctly. + + +### Valuable resources +- [Apple accessibility guidelines](https://developer.apple.com/design/human-interface-guidelines/accessibility/overview/introduction/) +- [Google accessibility guidelines](https://developer.android.com/guide/topics/ui/accessibility) +- [Web accessibility guidelines](https://www.w3.org/WAI/standards-guidelines/wcag/) \ No newline at end of file diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index a99bde77236f..b97d04b95e10 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -89,13 +89,13 @@ It’s possible that you found a new bug that we haven’t posted as a job to th Please follow these steps to propose a job or raise a bug: 1. Check to ensure a GH issue does not already exist for this job in the [New Expensify Issue list](https://github.com/Expensify/App/issues). -2. Check to ensure the `Bug:` or `Feature Request:` was not already posted in Slack (specifically the #expensify-bugs or #expensify-open-source [Slack channels](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#slack-channels)). Use your best judgement by searching for similar titles and issue descriptions. +2. Check to ensure the `Bug:` or `Feature Request:` was not already posted in Slack (specifically the #expensify-bugs or #expensify-open-source [Slack channels](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#slack-channels)). Use your best judgement by searching for similar titles, words and issue descriptions. 3. If your bug or new feature matches with an existing issue, please comment on that Slack thread or GitHub issue with your findings if you think it will help solve the issue. 4. If there is no existing GitHub issue or Upwork job, check if the issue is happening on prod (as opposed to only happening on dev) 5. If the issue is just in dev then it means it's a new issue and has not been deployed to production. In this case, you should try to find the offending PR and comment in the issue tied to the PR and ask the assigned users to add the `DeployBlockerCash` label. If you can't find it, follow the reporting instructions in the next item, but note that the issue is a regression only found in dev and not in prod. -6. If the issue happens in main, staging, or production then report the issue(s) in the #expensify-bugs Slack channel, using the report bug workflow. You can do this by clicking 'Workflow > report Bug', or typing `/Report bug`. View [this guide](https://github.com/Expensify/App/blob/main/contributingGuides/HOW_TO_CREATE_A_PLAN.md) for help creating a plan when proposing a feature request. +6. If the issue happens in main, staging, or production then report the issue(s) in the #expensify-bugs Slack channel, using the report bug workflow. You can do this by clicking 'Workflow > report Bug', or typing `/Report bug`. View [this guide](https://github.com/Expensify/App/blob/main/contributingGuides/HOW_TO_CREATE_A_PLAN.md) for help creating a plan when proposing a feature request. Please verify the bug's presence on **every** platform mentioned in the bug report template, and confirm this with a screen recording.. - **Important note/reminder**: never share any information pertaining to a customer of Expensify when describing the bug. This includes, and is not limited to, a customer's name, email, and contact information. -7. The Expensify team will review your job proposal in the appropriate slack channel. If you've provided a quality proposal that we choose to implement, a GitHub issue will be created and your Slack handle will be included in the original post after `Issue reported by:` +7. The Applause team will review your job proposal in the appropriate slack channel. If you've provided a quality proposal that we choose to implement, a GitHub issue will be created and your Slack handle will be included in the original post after `Issue reported by:` 8. If an external contributor other than yourself is hired to work on the issue, you will also be hired for the same job in Upwork to receive your payout. No additional work is required. If the issue is fixed internally, a dedicated job will be created to hire and pay you after the issue is fixed. 9. Payment will be made 7 days after code is deployed to production if there are no regressions. If a regression is discovered, payment will be issued 7 days after all regressions are fixed. diff --git a/contributingGuides/STYLE.md b/contributingGuides/STYLE.md index ce59438a0681..b615104f6aab 100644 --- a/contributingGuides/STYLE.md +++ b/contributingGuides/STYLE.md @@ -567,6 +567,28 @@ A `useEffect()` that does not include referenced props or state in its dependenc There are pros and cons of each, but ultimately we have standardized on using the `function` keyword to align things more with modern React conventions. There are also some minor cognitive overhead benefits in that you don't need to think about adding and removing brackets when encountering an implicit return. The `function` syntax also has the benefit of being able to be hoisted where arrow functions do not. +## How do I auto-focus a TextInput using `useFocusEffect()`? + +```javascript +const focusTimeoutRef = useRef(null); + +useFocusEffect(useCallback(() => { + focusTimeoutRef.current = setTimeout(() => textInputRef.current.focus(), CONST.ANIMATED_TRANSITION); + return () => { + if (!focusTimeoutRef.current) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; +}, [])); +``` + +This works better than using `onTransitionEnd` because - +1. `onTransitionEnd` is only fired for the top card in the stack, and therefore does not fire on the new top card when popping a card off the stack. For example - pressing the back button to go from the workspace invite page to the workspace members list. +2. Using `InteractionsManager.runAfterInteractions` with `useFocusEffect` will interrupt an in-progress transition animation. + +Note - This is a solution from [this PR](https://github.com/Expensify/App/pull/26415). You can find detailed discussion in comments. + # Onyx Best Practices [Onyx Documentation](https://github.com/expensify/react-native-onyx) diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index f3c4d0967e85..3a3aa7f765a8 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.71 + 1.3.72 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.71.8 + 1.3.72.6 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 8278e9baca09..340d56aa975c 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.71 + 1.3.72 CFBundleSignature ???? CFBundleVersion - 1.3.71.8 + 1.3.72.6 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b2e555dcfae6..51b9f6af0e21 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -31,14 +31,14 @@ PODS: - React-Core - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) - - FBLazyVector (0.72.3) - - FBReactNativeSpec (0.72.3): + - FBLazyVector (0.72.4) + - FBReactNativeSpec (0.72.4): - RCT-Folly (= 2021.07.22.00) - - RCTRequired (= 0.72.3) - - RCTTypeSafety (= 0.72.3) - - React-Core (= 0.72.3) - - React-jsi (= 0.72.3) - - ReactCommon/turbomodule/core (= 0.72.3) + - RCTRequired (= 0.72.4) + - RCTTypeSafety (= 0.72.4) + - React-Core (= 0.72.4) + - React-jsi (= 0.72.4) + - ReactCommon/turbomodule/core (= 0.72.4) - Firebase/Analytics (8.8.0): - Firebase/Core - Firebase/Core (8.8.0): @@ -220,9 +220,9 @@ PODS: - AppAuth/Core (~> 1.6) - GTMSessionFetcher/Core (< 4.0, >= 1.5) - GTMSessionFetcher/Core (3.1.1) - - hermes-engine (0.72.3): - - hermes-engine/Pre-built (= 0.72.3) - - hermes-engine/Pre-built (0.72.3) + - hermes-engine (0.72.4): + - hermes-engine/Pre-built (= 0.72.4) + - hermes-engine/Pre-built (0.72.4) - libevent (2.1.12) - libwebp (1.2.4): - libwebp/demux (= 1.2.4) @@ -283,26 +283,26 @@ PODS: - fmt (~> 6.2.1) - glog - libevent - - RCTRequired (0.72.3) - - RCTTypeSafety (0.72.3): - - FBLazyVector (= 0.72.3) - - RCTRequired (= 0.72.3) - - React-Core (= 0.72.3) - - React (0.72.3): - - React-Core (= 0.72.3) - - React-Core/DevSupport (= 0.72.3) - - React-Core/RCTWebSocket (= 0.72.3) - - React-RCTActionSheet (= 0.72.3) - - React-RCTAnimation (= 0.72.3) - - React-RCTBlob (= 0.72.3) - - React-RCTImage (= 0.72.3) - - React-RCTLinking (= 0.72.3) - - React-RCTNetwork (= 0.72.3) - - React-RCTSettings (= 0.72.3) - - React-RCTText (= 0.72.3) - - React-RCTVibration (= 0.72.3) - - React-callinvoker (0.72.3) - - React-Codegen (0.72.3): + - RCTRequired (0.72.4) + - RCTTypeSafety (0.72.4): + - FBLazyVector (= 0.72.4) + - RCTRequired (= 0.72.4) + - React-Core (= 0.72.4) + - React (0.72.4): + - React-Core (= 0.72.4) + - React-Core/DevSupport (= 0.72.4) + - React-Core/RCTWebSocket (= 0.72.4) + - React-RCTActionSheet (= 0.72.4) + - React-RCTAnimation (= 0.72.4) + - React-RCTBlob (= 0.72.4) + - React-RCTImage (= 0.72.4) + - React-RCTLinking (= 0.72.4) + - React-RCTNetwork (= 0.72.4) + - React-RCTSettings (= 0.72.4) + - React-RCTText (= 0.72.4) + - React-RCTVibration (= 0.72.4) + - React-callinvoker (0.72.4) + - React-Codegen (0.72.4): - DoubleConversion - FBReactNativeSpec - glog @@ -317,11 +317,11 @@ PODS: - React-rncore - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - React-Core (0.72.3): + - React-Core (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-Core/Default (= 0.72.3) + - React-Core/Default (= 0.72.4) - React-cxxreact - React-hermes - React-jsi @@ -331,7 +331,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/CoreModulesHeaders (0.72.3): + - React-Core/CoreModulesHeaders (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) @@ -345,7 +345,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/Default (0.72.3): + - React-Core/Default (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) @@ -358,23 +358,23 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/DevSupport (0.72.3): + - React-Core/DevSupport (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-Core/Default (= 0.72.3) - - React-Core/RCTWebSocket (= 0.72.3) + - React-Core/Default (= 0.72.4) + - React-Core/RCTWebSocket (= 0.72.4) - React-cxxreact - React-hermes - React-jsi - React-jsiexecutor - - React-jsinspector (= 0.72.3) + - React-jsinspector (= 0.72.4) - React-perflogger - React-runtimeexecutor - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTActionSheetHeaders (0.72.3): + - React-Core/RCTActionSheetHeaders (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) @@ -388,7 +388,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTAnimationHeaders (0.72.3): + - React-Core/RCTAnimationHeaders (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) @@ -402,7 +402,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTBlobHeaders (0.72.3): + - React-Core/RCTBlobHeaders (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) @@ -416,7 +416,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTImageHeaders (0.72.3): + - React-Core/RCTImageHeaders (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) @@ -430,7 +430,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTLinkingHeaders (0.72.3): + - React-Core/RCTLinkingHeaders (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) @@ -444,7 +444,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTNetworkHeaders (0.72.3): + - React-Core/RCTNetworkHeaders (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) @@ -458,7 +458,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTSettingsHeaders (0.72.3): + - React-Core/RCTSettingsHeaders (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) @@ -472,7 +472,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTTextHeaders (0.72.3): + - React-Core/RCTTextHeaders (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) @@ -486,7 +486,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTVibrationHeaders (0.72.3): + - React-Core/RCTVibrationHeaders (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) @@ -500,11 +500,11 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTWebSocket (0.72.3): + - React-Core/RCTWebSocket (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-Core/Default (= 0.72.3) + - React-Core/Default (= 0.72.4) - React-cxxreact - React-hermes - React-jsi @@ -514,57 +514,57 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-CoreModules (0.72.3): + - React-CoreModules (0.72.4): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.72.3) - - React-Codegen (= 0.72.3) - - React-Core/CoreModulesHeaders (= 0.72.3) - - React-jsi (= 0.72.3) + - RCTTypeSafety (= 0.72.4) + - React-Codegen (= 0.72.4) + - React-Core/CoreModulesHeaders (= 0.72.4) + - React-jsi (= 0.72.4) - React-RCTBlob - - React-RCTImage (= 0.72.3) - - ReactCommon/turbomodule/core (= 0.72.3) + - React-RCTImage (= 0.72.4) + - ReactCommon/turbomodule/core (= 0.72.4) - SocketRocket (= 0.6.1) - - React-cxxreact (0.72.3): + - React-cxxreact (0.72.4): - boost (= 1.76.0) - DoubleConversion - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-callinvoker (= 0.72.3) - - React-debug (= 0.72.3) - - React-jsi (= 0.72.3) - - React-jsinspector (= 0.72.3) - - React-logger (= 0.72.3) - - React-perflogger (= 0.72.3) - - React-runtimeexecutor (= 0.72.3) - - React-debug (0.72.3) - - React-hermes (0.72.3): + - React-callinvoker (= 0.72.4) + - React-debug (= 0.72.4) + - React-jsi (= 0.72.4) + - React-jsinspector (= 0.72.4) + - React-logger (= 0.72.4) + - React-perflogger (= 0.72.4) + - React-runtimeexecutor (= 0.72.4) + - React-debug (0.72.4) + - React-hermes (0.72.4): - DoubleConversion - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) - RCT-Folly/Futures (= 2021.07.22.00) - - React-cxxreact (= 0.72.3) + - React-cxxreact (= 0.72.4) - React-jsi - - React-jsiexecutor (= 0.72.3) - - React-jsinspector (= 0.72.3) - - React-perflogger (= 0.72.3) - - React-jsi (0.72.3): + - React-jsiexecutor (= 0.72.4) + - React-jsinspector (= 0.72.4) + - React-perflogger (= 0.72.4) + - React-jsi (0.72.4): - boost (= 1.76.0) - DoubleConversion - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-jsiexecutor (0.72.3): + - React-jsiexecutor (0.72.4): - DoubleConversion - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-cxxreact (= 0.72.3) - - React-jsi (= 0.72.3) - - React-perflogger (= 0.72.3) - - React-jsinspector (0.72.3) - - React-logger (0.72.3): + - React-cxxreact (= 0.72.4) + - React-jsi (= 0.72.4) + - React-perflogger (= 0.72.4) + - React-jsinspector (0.72.4) + - React-logger (0.72.4): - glog - react-native-airship (15.2.6): - AirshipFrameworkProxy (= 2.0.8) @@ -614,7 +614,7 @@ PODS: - React-Core - react-native-webview (11.23.0): - React-Core - - React-NativeModulesApple (0.72.3): + - React-NativeModulesApple (0.72.4): - hermes-engine - React-callinvoker - React-Core @@ -623,17 +623,17 @@ PODS: - React-runtimeexecutor - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - React-perflogger (0.72.3) - - React-RCTActionSheet (0.72.3): - - React-Core/RCTActionSheetHeaders (= 0.72.3) - - React-RCTAnimation (0.72.3): + - React-perflogger (0.72.4) + - React-RCTActionSheet (0.72.4): + - React-Core/RCTActionSheetHeaders (= 0.72.4) + - React-RCTAnimation (0.72.4): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.72.3) - - React-Codegen (= 0.72.3) - - React-Core/RCTAnimationHeaders (= 0.72.3) - - React-jsi (= 0.72.3) - - ReactCommon/turbomodule/core (= 0.72.3) - - React-RCTAppDelegate (0.72.3): + - RCTTypeSafety (= 0.72.4) + - React-Codegen (= 0.72.4) + - React-Core/RCTAnimationHeaders (= 0.72.4) + - React-jsi (= 0.72.4) + - ReactCommon/turbomodule/core (= 0.72.4) + - React-RCTAppDelegate (0.72.4): - RCT-Folly - RCTRequired - RCTTypeSafety @@ -645,54 +645,54 @@ PODS: - React-RCTNetwork - React-runtimescheduler - ReactCommon/turbomodule/core - - React-RCTBlob (0.72.3): + - React-RCTBlob (0.72.4): - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-Codegen (= 0.72.3) - - React-Core/RCTBlobHeaders (= 0.72.3) - - React-Core/RCTWebSocket (= 0.72.3) - - React-jsi (= 0.72.3) - - React-RCTNetwork (= 0.72.3) - - ReactCommon/turbomodule/core (= 0.72.3) - - React-RCTImage (0.72.3): + - React-Codegen (= 0.72.4) + - React-Core/RCTBlobHeaders (= 0.72.4) + - React-Core/RCTWebSocket (= 0.72.4) + - React-jsi (= 0.72.4) + - React-RCTNetwork (= 0.72.4) + - ReactCommon/turbomodule/core (= 0.72.4) + - React-RCTImage (0.72.4): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.72.3) - - React-Codegen (= 0.72.3) - - React-Core/RCTImageHeaders (= 0.72.3) - - React-jsi (= 0.72.3) - - React-RCTNetwork (= 0.72.3) - - ReactCommon/turbomodule/core (= 0.72.3) - - React-RCTLinking (0.72.3): - - React-Codegen (= 0.72.3) - - React-Core/RCTLinkingHeaders (= 0.72.3) - - React-jsi (= 0.72.3) - - ReactCommon/turbomodule/core (= 0.72.3) - - React-RCTNetwork (0.72.3): + - RCTTypeSafety (= 0.72.4) + - React-Codegen (= 0.72.4) + - React-Core/RCTImageHeaders (= 0.72.4) + - React-jsi (= 0.72.4) + - React-RCTNetwork (= 0.72.4) + - ReactCommon/turbomodule/core (= 0.72.4) + - React-RCTLinking (0.72.4): + - React-Codegen (= 0.72.4) + - React-Core/RCTLinkingHeaders (= 0.72.4) + - React-jsi (= 0.72.4) + - ReactCommon/turbomodule/core (= 0.72.4) + - React-RCTNetwork (0.72.4): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.72.3) - - React-Codegen (= 0.72.3) - - React-Core/RCTNetworkHeaders (= 0.72.3) - - React-jsi (= 0.72.3) - - ReactCommon/turbomodule/core (= 0.72.3) - - React-RCTSettings (0.72.3): + - RCTTypeSafety (= 0.72.4) + - React-Codegen (= 0.72.4) + - React-Core/RCTNetworkHeaders (= 0.72.4) + - React-jsi (= 0.72.4) + - ReactCommon/turbomodule/core (= 0.72.4) + - React-RCTSettings (0.72.4): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.72.3) - - React-Codegen (= 0.72.3) - - React-Core/RCTSettingsHeaders (= 0.72.3) - - React-jsi (= 0.72.3) - - ReactCommon/turbomodule/core (= 0.72.3) - - React-RCTText (0.72.3): - - React-Core/RCTTextHeaders (= 0.72.3) - - React-RCTVibration (0.72.3): + - RCTTypeSafety (= 0.72.4) + - React-Codegen (= 0.72.4) + - React-Core/RCTSettingsHeaders (= 0.72.4) + - React-jsi (= 0.72.4) + - ReactCommon/turbomodule/core (= 0.72.4) + - React-RCTText (0.72.4): + - React-Core/RCTTextHeaders (= 0.72.4) + - React-RCTVibration (0.72.4): - RCT-Folly (= 2021.07.22.00) - - React-Codegen (= 0.72.3) - - React-Core/RCTVibrationHeaders (= 0.72.3) - - React-jsi (= 0.72.3) - - ReactCommon/turbomodule/core (= 0.72.3) - - React-rncore (0.72.3) - - React-runtimeexecutor (0.72.3): - - React-jsi (= 0.72.3) - - React-runtimescheduler (0.72.3): + - React-Codegen (= 0.72.4) + - React-Core/RCTVibrationHeaders (= 0.72.4) + - React-jsi (= 0.72.4) + - ReactCommon/turbomodule/core (= 0.72.4) + - React-rncore (0.72.4) + - React-runtimeexecutor (0.72.4): + - React-jsi (= 0.72.4) + - React-runtimescheduler (0.72.4): - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) @@ -700,30 +700,30 @@ PODS: - React-debug - React-jsi - React-runtimeexecutor - - React-utils (0.72.3): + - React-utils (0.72.4): - glog - RCT-Folly (= 2021.07.22.00) - React-debug - - ReactCommon/turbomodule/bridging (0.72.3): + - ReactCommon/turbomodule/bridging (0.72.4): - DoubleConversion - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-callinvoker (= 0.72.3) - - React-cxxreact (= 0.72.3) - - React-jsi (= 0.72.3) - - React-logger (= 0.72.3) - - React-perflogger (= 0.72.3) - - ReactCommon/turbomodule/core (0.72.3): + - React-callinvoker (= 0.72.4) + - React-cxxreact (= 0.72.4) + - React-jsi (= 0.72.4) + - React-logger (= 0.72.4) + - React-perflogger (= 0.72.4) + - ReactCommon/turbomodule/core (0.72.4): - DoubleConversion - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-callinvoker (= 0.72.3) - - React-cxxreact (= 0.72.3) - - React-jsi (= 0.72.3) - - React-logger (= 0.72.3) - - React-perflogger (= 0.72.3) + - React-callinvoker (= 0.72.4) + - React-cxxreact (= 0.72.4) + - React-jsi (= 0.72.4) + - React-logger (= 0.72.4) + - React-perflogger (= 0.72.4) - RNAppleAuthentication (2.2.2): - React-Core - RNCAsyncStorage (1.17.11): @@ -1010,7 +1010,7 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" - :tag: hermes-2023-03-20-RNv0.72.0-49794cfc7c81fb8f69fd60c3bbf85a7480cc5a77 + :tag: hermes-2023-08-07-RNv0.72.4-813b2def12bc9df02654b3e3653ae4a68d0572e0 lottie-react-native: :path: "../node_modules/lottie-react-native" onfido-react-native-sdk: @@ -1182,8 +1182,8 @@ SPEC CHECKSUMS: BVLinearGradient: 421743791a59d259aec53f4c58793aad031da2ca CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 - FBLazyVector: 4cce221dd782d3ff7c4172167bba09d58af67ccb - FBReactNativeSpec: c6bd9e179757b3c0ecf815864fae8032377903ef + FBLazyVector: 5d4a3b7f411219a45a6d952f77d2c0a6c9989da5 + FBReactNativeSpec: 3fc2d478e1c4b08276f9dd9128f80ec6d5d85c1f Firebase: 629510f1a9ddb235f3a7c5c8ceb23ba887f0f814 FirebaseABTesting: 10cbce8db9985ae2e3847ea44e9947dd18f94e10 FirebaseAnalytics: 5506ea8b867d8423485a84b4cd612d279f7b0b8a @@ -1209,7 +1209,7 @@ SPEC CHECKSUMS: GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749 GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae GTMSessionFetcher: e8647203b65cee28c5f73d0f473d096653945e72 - hermes-engine: 10fbd3f62405c41ea07e71973ea61e1878d07322 + hermes-engine: 81191603c4eaa01f5e4ae5737a9efcf64756c7b2 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef lottie-ios: 8f97d3271e155c2d688875c29cd3c74908aef5f8 @@ -1229,20 +1229,20 @@ SPEC CHECKSUMS: Plaid: 7d340abeadb46c7aa1a91f896c5b22395a31fcf2 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 - RCTRequired: a2faf4bad4e438ca37b2040cb8f7799baa065c18 - RCTTypeSafety: cb09f3e4747b6d18331a15eb05271de7441ca0b3 - React: 13109005b5353095c052f26af37413340ccf7a5d - React-callinvoker: c8c87bce983aa499c13cb06d4447c025a35274d6 - React-Codegen: 712d523524d89d71f1cf7cc624854941be983c4d - React-Core: 688f88b7f3a3d30b4848036223f8b07102c687e5 - React-CoreModules: 63c063a3ade8fb3b1bec5fd9a50f17b0421558c6 - React-cxxreact: 37765b4975541105b2a3322a4b473417c158c869 - React-debug: 51f11ef8db14b47f24e71c42a4916d4192972156 - React-hermes: 935ae71fb3d7654e947beba8498835cd5e479707 - React-jsi: ec628dc7a15ffea969f237b0ea6d2fde212b19dd - React-jsiexecutor: 59d1eb03af7d30b7d66589c410f13151271e8006 - React-jsinspector: b511447170f561157547bc0bef3f169663860be7 - React-logger: c5b527272d5f22eaa09bb3c3a690fee8f237ae95 + RCTRequired: c0569ecc035894e4a68baecb30fe6a7ea6e399f9 + RCTTypeSafety: e90354072c21236e0bcf1699011e39acd25fea2f + React: a1be3c6dc0a6e949ccd3e659781aa47bbae1868f + React-callinvoker: 1020b33f6cb1a1824f9ca2a86609fbce2a73c6ed + React-Codegen: a0a26badf098d4a779acda922caf74f6ecabed28 + React-Core: 52075b80f10c26f62219d7b5d13d7d8089f027b3 + React-CoreModules: 21abab85d7ad9038ce2b1c33d39e3baaf7dc9244 + React-cxxreact: 4ad1cc861e32fb533dad6ff7a4ea25680fa1c994 + React-debug: 17366a3d5c5d2f5fc04f09101a4af38cb42b54ae + React-hermes: 37377d0a56aa0cf55c65248271866ce3268cde3f + React-jsi: 6de8b0ccc6b765b58e4eee9ee38049dbeaf5c221 + React-jsiexecutor: c7f826e40fa9cab5d37cab6130b1af237332b594 + React-jsinspector: aaed4cf551c4a1c98092436518c2d267b13a673f + React-logger: da1ebe05ae06eb6db4b162202faeafac4b435e77 react-native-airship: 5d19f4ba303481cf4101ff9dee9249ef6a8a6b64 react-native-blob-util: 99f4d79189252f597fe0d810c57a3733b1b1dea6 react-native-cameraroll: 8ffb0af7a5e5de225fd667610e2979fc1f0c2151 @@ -1262,23 +1262,23 @@ SPEC CHECKSUMS: react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a react-native-view-shot: 705f999ac2a24e4e6c909c0ca65c732ed33ca2ff react-native-webview: e771bc375f789ebfa02a26939a57dbc6fa897336 - React-NativeModulesApple: c57f3efe0df288a6532b726ad2d0322a9bf38472 - React-perflogger: 6bd153e776e6beed54c56b0847e1220a3ff92ba5 - React-RCTActionSheet: c0b62af44e610e69d9a2049a682f5dba4e9dff17 - React-RCTAnimation: f9bf9719258926aea9ecb8a2aa2595d3ff9a6022 - React-RCTAppDelegate: e5ac35d4dbd1fae7df3a62b47db04b6a8d151592 - React-RCTBlob: c4f1e69a6ef739aa42586b876d637dab4e3b5bed - React-RCTImage: e5798f01aba248416c02a506cf5e6dfcba827638 - React-RCTLinking: f5b6227c879e33206f34e68924c458f57bbb96d9 - React-RCTNetwork: d5554fbfac1c618da3c8fa29933108ea22837788 - React-RCTSettings: 189c71e3e6146ba59f4f7e2cbeb494cf2ad42afa - React-RCTText: 19425aea9d8b6ccae55a27916355b17ab577e56e - React-RCTVibration: 388ac0e1455420895d1ca2548401eed964b038a6 - React-rncore: 755a331dd67b74662108f2d66a384454bf8dc1a1 - React-runtimeexecutor: 369ae9bb3f83b65201c0c8f7d50b72280b5a1dbc - React-runtimescheduler: 837c1bebd2f84572db17698cd702ceaf585b0d9a - React-utils: bcb57da67eec2711f8b353f6e3d33bd8e4b2efa3 - ReactCommon: 3ccb8fb14e6b3277e38c73b0ff5e4a1b8db017a9 + React-NativeModulesApple: edb5ace14f73f4969df6e7b1f3e41bef0012740f + React-perflogger: 496a1a3dc6737f964107cb3ddae7f9e265ddda58 + React-RCTActionSheet: 02904b932b50e680f4e26e7a686b33ebf7ef3c00 + React-RCTAnimation: 88feaf0a85648fb8fd497ce749829774910276d6 + React-RCTAppDelegate: 5792ac0f0feccb584765fdd7aa81ea320c4d9b0b + React-RCTBlob: 0dbc9e2a13d241b37d46b53e54630cbad1f0e141 + React-RCTImage: b111645ab901f8e59fc68fbe31f5731bdbeef087 + React-RCTLinking: 3d719727b4c098aad3588aa3559361ee0579f5de + React-RCTNetwork: b44d3580be05d74556ba4efbf53570f17e38f734 + React-RCTSettings: c0c54b330442c29874cd4dae6e94190dc11a6f6f + React-RCTText: 9b9f5589d9b649d7246c3f336e116496df28cfe6 + React-RCTVibration: 691c67f3beaf1d084ceed5eb5c1dddd9afa8591e + React-rncore: 142268f6c92e296dc079aadda3fade778562f9e4 + React-runtimeexecutor: d465ba0c47ef3ed8281143f59605cacc2244d5c7 + React-runtimescheduler: 4941cc1b3cf08b792fbf666342c9fc95f1969035 + React-utils: b79f2411931f9d3ea5781404dcbb2fa8a837e13a + ReactCommon: 4b2bdcb50a3543e1c2b2849ad44533686610826d RNAppleAuthentication: 0571c08da8c327ae2afc0261b48b4a515b0286a6 RNCAsyncStorage: 8616bd5a58af409453ea4e1b246521bb76578d60 RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495 @@ -1306,7 +1306,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 469ce2c3d22e5e8e4818d5a3b254699a5c89efa4 VisionCamera: d3ec8883417a6a4a0e3a6ba37d81d22db7611601 - Yoga: 8796b55dba14d7004f980b54bcc9833ee45b28ce + Yoga: 3efc43e0d48686ce2e8c60f99d4e6bd349aff981 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a PODFILE CHECKSUM: 2daf34c870819a933f3fefe426801d54b2ff2a14 diff --git a/jest.config.js b/jest.config.js index 1f540a679b9a..6cf44b6b3695 100644 --- a/jest.config.js +++ b/jest.config.js @@ -23,6 +23,6 @@ module.exports = { }, testEnvironment: 'jsdom', setupFiles: ['/jest/setup.js', './node_modules/@react-native-google-signin/google-signin/jest/build/setup.js'], - setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'], + setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect', '/jest/setupAfterEnv.js'], cacheDirectory: '/.jest-cache', }; diff --git a/jest/setupAfterEnv.js b/jest/setupAfterEnv.js new file mode 100644 index 000000000000..6f7836b64dbb --- /dev/null +++ b/jest/setupAfterEnv.js @@ -0,0 +1 @@ +jest.useRealTimers(); diff --git a/package-lock.json b/package-lock.json index 53bbac4c680b..64abe30d6187 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.71-8", + "version": "1.3.72-6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.71-8", + "version": "1.3.72-6", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -71,7 +71,7 @@ "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", "react-map-gl": "^7.1.3", - "react-native": "0.72.3", + "react-native": "0.72.4", "react-native-blob-util": "^0.17.3", "react-native-collapsible": "^1.6.0", "react-native-config": "^1.4.5", @@ -81,7 +81,7 @@ "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.12.0", - "react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#fd212e1e93cad72e97efad03893bea6d074d3e07", + "react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#cef3ac29d9501091453136e1219e24c4ec9f9d76", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^5.1.0", @@ -90,7 +90,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "^1.0.84", + "react-native-onyx": "1.0.84", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", @@ -139,7 +139,7 @@ "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", "@react-native-community/eslint-config": "3.0.0", - "@react-native/metro-config": "^0.72.9", + "@react-native/metro-config": "^0.72.11", "@react-navigation/devtools": "^6.0.10", "@storybook/addon-a11y": "^6.5.9", "@storybook/addon-essentials": "^7.0.0", @@ -204,7 +204,7 @@ "jest-circus": "29.4.1", "jest-cli": "29.4.1", "jest-environment-jsdom": "^29.4.1", - "metro-react-native-babel-preset": "0.76.7", + "metro-react-native-babel-preset": "0.76.8", "mock-fs": "^4.13.0", "onchange": "^7.1.0", "portfinder": "^1.0.28", @@ -6726,19 +6726,19 @@ } }, "node_modules/@react-native-community/cli": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-11.3.5.tgz", - "integrity": "sha512-wMXgKEWe6uesw7vyXKKjx5EDRog0QdXHxdgRguG14AjQRao1+4gXEWq2yyExOTi/GDY6dfJBUGTCwGQxhnk/Lg==", - "dependencies": { - "@react-native-community/cli-clean": "11.3.5", - "@react-native-community/cli-config": "11.3.5", - "@react-native-community/cli-debugger-ui": "11.3.5", - "@react-native-community/cli-doctor": "11.3.5", - "@react-native-community/cli-hermes": "11.3.5", - "@react-native-community/cli-plugin-metro": "11.3.5", - "@react-native-community/cli-server-api": "11.3.5", - "@react-native-community/cli-tools": "11.3.5", - "@react-native-community/cli-types": "11.3.5", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-11.3.6.tgz", + "integrity": "sha512-bdwOIYTBVQ9VK34dsf6t3u6vOUU5lfdhKaAxiAVArjsr7Je88Bgs4sAbsOYsNK3tkE8G77U6wLpekknXcanlww==", + "dependencies": { + "@react-native-community/cli-clean": "11.3.6", + "@react-native-community/cli-config": "11.3.6", + "@react-native-community/cli-debugger-ui": "11.3.6", + "@react-native-community/cli-doctor": "11.3.6", + "@react-native-community/cli-hermes": "11.3.6", + "@react-native-community/cli-plugin-metro": "11.3.6", + "@react-native-community/cli-server-api": "11.3.6", + "@react-native-community/cli-tools": "11.3.6", + "@react-native-community/cli-types": "11.3.6", "chalk": "^4.1.2", "commander": "^9.4.1", "execa": "^5.0.0", @@ -6746,7 +6746,7 @@ "fs-extra": "^8.1.0", "graceful-fs": "^4.1.3", "prompts": "^2.4.0", - "semver": "^6.3.0" + "semver": "^7.5.2" }, "bin": { "react-native": "build/bin.js" @@ -6756,11 +6756,11 @@ } }, "node_modules/@react-native-community/cli-clean": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-11.3.5.tgz", - "integrity": "sha512-1+7BU962wKkIkHRp/uW3jYbQKKGtU7L+R3g59D8K6uLccuxJYUBJv18753ojMa6SD3SAq5Xh31bAre+YwVcOTA==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-11.3.6.tgz", + "integrity": "sha512-jOOaeG5ebSXTHweq1NznVJVAFKtTFWL4lWgUXl845bCGX7t1lL8xQNWHKwT8Oh1pGR2CI3cKmRjY4hBg+pEI9g==", "dependencies": { - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "execa": "^5.0.0", "prompts": "^2.4.0" @@ -6831,11 +6831,11 @@ } }, "node_modules/@react-native-community/cli-config": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-11.3.5.tgz", - "integrity": "sha512-fMblIsHlUleKfGsgWyjFJYfx1SqrsnhS/QXfA8w7iT6GrNOOjBp5UWx8+xlMDFcmOb9e42g1ExFDKl3n8FWkxQ==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-11.3.6.tgz", + "integrity": "sha512-edy7fwllSFLan/6BG6/rznOBCLPrjmJAE10FzkEqNLHowi0bckiAPg1+1jlgQ2qqAxV5kuk+c9eajVfQvPLYDA==", "dependencies": { - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "cosmiconfig": "^5.1.0", "deepmerge": "^4.3.0", @@ -6954,22 +6954,22 @@ } }, "node_modules/@react-native-community/cli-debugger-ui": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-11.3.5.tgz", - "integrity": "sha512-o5JVCKEpPUXMX4r3p1cYjiy3FgdOEkezZcQ6owWEae2dYvV19lLYyJwnocm9Y7aG9PvpgI3PIMVh3KZbhS21eA==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-11.3.6.tgz", + "integrity": "sha512-jhMOSN/iOlid9jn/A2/uf7HbC3u7+lGktpeGSLnHNw21iahFBzcpuO71ekEdlmTZ4zC/WyxBXw9j2ka33T358w==", "dependencies": { "serve-static": "^1.13.1" } }, "node_modules/@react-native-community/cli-doctor": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-11.3.5.tgz", - "integrity": "sha512-+4BuFHjoV4FFjX5y60l0s6nS0agidb1izTVwsFixeFKW73LUkOLu+Ae5HI94RAFEPE4ePEVNgYX3FynIau6K0g==", - "dependencies": { - "@react-native-community/cli-config": "11.3.5", - "@react-native-community/cli-platform-android": "11.3.5", - "@react-native-community/cli-platform-ios": "11.3.5", - "@react-native-community/cli-tools": "11.3.5", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-11.3.6.tgz", + "integrity": "sha512-UT/Tt6omVPi1j6JEX+CObc85eVFghSZwy4GR9JFMsO7gNg2Tvcu1RGWlUkrbmWMAMHw127LUu6TGK66Ugu1NLA==", + "dependencies": { + "@react-native-community/cli-config": "11.3.6", + "@react-native-community/cli-platform-android": "11.3.6", + "@react-native-community/cli-platform-ios": "11.3.6", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "command-exists": "^1.2.8", "envinfo": "^7.7.2", @@ -6979,7 +6979,7 @@ "node-stream-zip": "^1.9.1", "ora": "^5.4.1", "prompts": "^2.4.0", - "semver": "^6.3.0", + "semver": "^7.5.2", "strip-ansi": "^5.2.0", "sudo-prompt": "^9.0.0", "wcwidth": "^1.0.1", @@ -7044,14 +7044,6 @@ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" }, - "node_modules/@react-native-community/cli-doctor/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -7075,12 +7067,12 @@ } }, "node_modules/@react-native-community/cli-hermes": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-11.3.5.tgz", - "integrity": "sha512-+3m34hiaJpFel8BlJE7kJOaPzWR/8U8APZG2LXojbAdBAg99EGmQcwXIgsSVJFvH8h/nezf4DHbsPKigIe33zA==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-11.3.6.tgz", + "integrity": "sha512-O55YAYGZ3XynpUdePPVvNuUPGPY0IJdctLAOHme73OvS80gNwfntHDXfmY70TGHWIfkK2zBhA0B+2v8s5aTyTA==", "dependencies": { - "@react-native-community/cli-platform-android": "11.3.5", - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-platform-android": "11.3.6", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "hermes-profile-transformer": "^0.0.6", "ip": "^1.1.5" @@ -7156,11 +7148,11 @@ } }, "node_modules/@react-native-community/cli-platform-android": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-11.3.5.tgz", - "integrity": "sha512-s4Lj7FKxJ/BofGi/ifjPfrA9MjFwIgYpHnHBSlqtbsvPoSYzmVCU2qlWM8fb3AmkXIwyYt4A6MEr3MmNT2UoBg==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-11.3.6.tgz", + "integrity": "sha512-ZARrpLv5tn3rmhZc//IuDM1LSAdYnjUmjrp58RynlvjLDI4ZEjBAGCQmgysRgXAsK7ekMrfkZgemUczfn9td2A==", "dependencies": { - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "execa": "^5.0.0", "glob": "^7.1.3", @@ -7232,11 +7224,11 @@ } }, "node_modules/@react-native-community/cli-platform-ios": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-11.3.5.tgz", - "integrity": "sha512-ytJC/YCFD7P+KuQHOT5Jzh1ho2XbJEjq71yHa1gJP2PG/Q/uB4h1x2XpxDqv5iXU6E250yjvKMmkReKTW4CTig==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-11.3.6.tgz", + "integrity": "sha512-tZ9VbXWiRW+F+fbZzpLMZlj93g3Q96HpuMsS6DRhrTiG+vMQ3o6oPWSEEmMGOvJSYU7+y68Dc9ms2liC7VD6cw==", "dependencies": { - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-xml-parser": "^4.0.12", @@ -7309,12 +7301,12 @@ } }, "node_modules/@react-native-community/cli-plugin-metro": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-11.3.5.tgz", - "integrity": "sha512-r9AekfeLKdblB7LfWB71IrNy1XM03WrByQlUQajUOZAP2NmUUBLl9pMZscPjJeOSgLpHB9ixEFTIOhTabri/qg==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-11.3.6.tgz", + "integrity": "sha512-D97racrPX3069ibyabJNKw9aJpVcaZrkYiEzsEnx50uauQtPDoQ1ELb/5c6CtMhAEGKoZ0B5MS23BbsSZcLs2g==", "dependencies": { - "@react-native-community/cli-server-api": "11.3.5", - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-server-api": "11.3.6", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "execa": "^5.0.0", "metro": "0.76.7", @@ -7326,6 +7318,29 @@ "readline": "^1.3.0" } }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/@types/yargs": { + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", + "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, "node_modules/@react-native-community/cli-plugin-metro/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -7355,6 +7370,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@react-native-community/cli-plugin-metro/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7371,6 +7404,28 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, "node_modules/@react-native-community/cli-plugin-metro/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7379,6 +7434,492 @@ "node": ">=8" } }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/jest-regex-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/jest-util/node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.76.7.tgz", + "integrity": "sha512-67ZGwDeumEPnrHI+pEDSKH2cx+C81Gx8Mn5qOtmGUPm/Up9Y4I1H2dJZ5n17MWzejNo0XAvPh0QL0CrlJEODVQ==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "accepts": "^1.3.7", + "async": "^3.2.2", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "denodeify": "^1.2.1", + "error-stack-parser": "^2.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.12.0", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^27.2.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.76.7", + "metro-cache": "0.76.7", + "metro-cache-key": "0.76.7", + "metro-config": "0.76.7", + "metro-core": "0.76.7", + "metro-file-map": "0.76.7", + "metro-inspector-proxy": "0.76.7", + "metro-minify-terser": "0.76.7", + "metro-minify-uglify": "0.76.7", + "metro-react-native-babel-preset": "0.76.7", + "metro-resolver": "0.76.7", + "metro-runtime": "0.76.7", + "metro-source-map": "0.76.7", + "metro-symbolicate": "0.76.7", + "metro-transform-plugins": "0.76.7", + "metro-transform-worker": "0.76.7", + "mime-types": "^2.1.27", + "node-fetch": "^2.2.0", + "nullthrows": "^1.1.1", + "rimraf": "^3.0.2", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "strip-ansi": "^6.0.0", + "throat": "^5.0.0", + "ws": "^7.5.1", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-babel-transformer": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.76.7.tgz", + "integrity": "sha512-bgr2OFn0J4r0qoZcHrwEvccF7g9k3wdgTOgk6gmGHrtlZ1Jn3oCpklW/DfZ9PzHfjY2mQammKTc19g/EFGyOJw==", + "dependencies": { + "@babel/core": "^7.20.0", + "hermes-parser": "0.12.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-cache": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.76.7.tgz", + "integrity": "sha512-nWBMztrs5RuSxZRI7hgFgob5PhYDmxICh9FF8anm9/ito0u0vpPvRxt7sRu8fyeD2AHdXqE7kX32rWY0LiXgeg==", + "dependencies": { + "metro-core": "0.76.7", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-cache-key": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.76.7.tgz", + "integrity": "sha512-0pecoIzwsD/Whn/Qfa+SDMX2YyasV0ndbcgUFx7w1Ct2sLHClujdhQ4ik6mvQmsaOcnGkIyN0zcceMDjC2+BFQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-config": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.76.7.tgz", + "integrity": "sha512-CFDyNb9bqxZemiChC/gNdXZ7OQkIwmXzkrEXivcXGbgzlt/b2juCv555GWJHyZSlorwnwJfY3uzAFu4A9iRVfg==", + "dependencies": { + "connect": "^3.6.5", + "cosmiconfig": "^5.0.5", + "jest-validate": "^29.2.1", + "metro": "0.76.7", + "metro-cache": "0.76.7", + "metro-core": "0.76.7", + "metro-runtime": "0.76.7" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-core": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.76.7.tgz", + "integrity": "sha512-0b8KfrwPmwCMW+1V7ZQPkTy2tsEKZjYG9Pu1PTsu463Z9fxX7WaR0fcHFshv+J1CnQSUTwIGGjbNvj1teKe+pw==", + "dependencies": { + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.76.7" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-file-map": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.76.7.tgz", + "integrity": "sha512-s+zEkTcJ4mOJTgEE2ht4jIo1DZfeWreQR3tpT3gDV/Y/0UQ8aJBTv62dE775z0GLsWZApiblAYZsj7ZE8P06nw==", + "dependencies": { + "anymatch": "^3.0.3", + "debug": "^2.2.0", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-regex-util": "^27.0.6", + "jest-util": "^27.2.0", + "jest-worker": "^27.2.0", + "micromatch": "^4.0.4", + "node-abort-controller": "^3.1.1", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-inspector-proxy": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.76.7.tgz", + "integrity": "sha512-rNZ/6edTl/1qUekAhAbaFjczMphM50/UjtxiKulo6vqvgn/Mjd9hVqDvVYfAMZXqPvlusD88n38UjVYPkruLSg==", + "dependencies": { + "connect": "^3.6.5", + "debug": "^2.2.0", + "node-fetch": "^2.2.0", + "ws": "^7.5.1", + "yargs": "^17.6.2" + }, + "bin": { + "metro-inspector-proxy": "src/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-minify-terser": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.76.7.tgz", + "integrity": "sha512-FQiZGhIxCzhDwK4LxyPMLlq0Tsmla10X7BfNGlYFK0A5IsaVKNJbETyTzhpIwc+YFRT4GkFFwgo0V2N5vxO5HA==", + "dependencies": { + "terser": "^5.15.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-minify-uglify": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.76.7.tgz", + "integrity": "sha512-FuXIU3j2uNcSvQtPrAJjYWHruPiQ+EpE++J9Z+VznQKEHcIxMMoQZAfIF2IpZSrZYfLOjVFyGMvj41jQMxV1Vw==", + "dependencies": { + "uglify-es": "^3.1.9" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-react-native-babel-preset": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.7.tgz", + "integrity": "sha512-R25wq+VOSorAK3hc07NW0SmN8z9S/IR0Us0oGAsBcMZnsgkbOxu77Mduqf+f4is/wnWHc5+9bfiqdLnaMngiVw==", + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.18.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.20.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.4.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-react-native-babel-transformer": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.76.7.tgz", + "integrity": "sha512-W6lW3J7y/05ph3c2p3KKJNhH0IdyxdOCbQ5it7aM2MAl0SM4wgKjaV6EYv9b3rHklpV6K3qMH37UKVcjMooWiA==", + "dependencies": { + "@babel/core": "^7.20.0", + "babel-preset-fbjs": "^3.4.0", + "hermes-parser": "0.12.0", + "metro-react-native-babel-preset": "0.76.7", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-resolver": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.76.7.tgz", + "integrity": "sha512-pC0Wgq29HHIHrwz23xxiNgylhI8Rq1V01kQaJ9Kz11zWrIdlrH0ZdnJ7GC6qA0ErROG+cXmJ0rJb8/SW1Zp2IA==", + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-runtime": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.76.7.tgz", + "integrity": "sha512-MuWHubQHymUWBpZLwuKZQgA/qbb35WnDAKPo83rk7JRLIFPvzXSvFaC18voPuzJBt1V98lKQIonh6MiC9gd8Ug==", + "dependencies": { + "@babel/runtime": "^7.0.0", + "react-refresh": "^0.4.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-source-map": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.76.7.tgz", + "integrity": "sha512-Prhx7PeRV1LuogT0Kn5VjCuFu9fVD68eefntdWabrksmNY6mXK8pRqzvNJOhTojh6nek+RxBzZeD6MIOOyXS6w==", + "dependencies": { + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "invariant": "^2.2.4", + "metro-symbolicate": "0.76.7", + "nullthrows": "^1.1.1", + "ob1": "0.76.7", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-symbolicate": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.76.7.tgz", + "integrity": "sha512-p0zWEME5qLSL1bJb93iq+zt5fz3sfVn9xFYzca1TJIpY5MommEaS64Va87lp56O0sfEIvh4307Oaf/ZzRjuLiQ==", + "dependencies": { + "invariant": "^2.2.4", + "metro-source-map": "0.76.7", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-transform-plugins": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.76.7.tgz", + "integrity": "sha512-iSmnjVApbdivjuzb88Orb0JHvcEt5veVyFAzxiS5h0QB+zV79w6JCSqZlHCrbNOkOKBED//LqtKbFVakxllnNg==", + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/metro-transform-worker": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.76.7.tgz", + "integrity": "sha512-cGvELqFMVk9XTC15CMVzrCzcO6sO1lURfcbgjuuPdzaWuD11eEyocvkTX0DPiRjsvgAmicz4XYxVzgYl3MykDw==", + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/types": "^7.20.0", + "babel-preset-fbjs": "^3.4.0", + "metro": "0.76.7", + "metro-babel-transformer": "0.76.7", + "metro-cache": "0.76.7", + "metro-cache-key": "0.76.7", + "metro-source-map": "0.76.7", + "metro-transform-plugins": "0.76.7", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/ob1": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.76.7.tgz", + "integrity": "sha512-BQdRtxxoUNfSoZxqeBGOyuT9nEYSn18xZHwGMb0mMVpn2NBcYbnyKY4BK2LIHRgw33CBGlUmE+KMaNvyTpLLtQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/react-refresh": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", + "integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@react-native-community/cli-plugin-metro/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7390,13 +7931,66 @@ "node": ">=8" } }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@react-native-community/cli-plugin-metro/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, "node_modules/@react-native-community/cli-server-api": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-11.3.5.tgz", - "integrity": "sha512-PM/jF13uD1eAKuC84lntNuM5ZvJAtyb+H896P1dBIXa9boPLa3KejfUvNVoyOUJ5s8Ht25JKbc3yieV2+GMBDA==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-11.3.6.tgz", + "integrity": "sha512-8GUKodPnURGtJ9JKg8yOHIRtWepPciI3ssXVw5jik7+dZ43yN8P5BqCoDaq8e1H1yRer27iiOfT7XVnwk8Dueg==", "dependencies": { - "@react-native-community/cli-debugger-ui": "11.3.5", - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-debugger-ui": "11.3.6", + "@react-native-community/cli-tools": "11.3.6", "compression": "^1.7.1", "connect": "^3.6.5", "errorhandler": "^1.5.1", @@ -7484,9 +8078,9 @@ } }, "node_modules/@react-native-community/cli-tools": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-11.3.5.tgz", - "integrity": "sha512-zDklE1+ah/zL4BLxut5XbzqCj9KTHzbYBKX7//cXw2/0TpkNCaY9c+iKx//gZ5m7U1OKbb86Fm2b0AKtKVRf6Q==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-11.3.6.tgz", + "integrity": "sha512-JpmUTcDwAGiTzLsfMlIAYpCMSJ9w2Qlf7PU7mZIRyEu61UzEawyw83DkqfbzDPBuRwRnaeN44JX2CP/yTO3ThQ==", "dependencies": { "appdirsjs": "^1.2.4", "chalk": "^4.1.2", @@ -7495,7 +8089,7 @@ "node-fetch": "^2.6.0", "open": "^6.2.0", "ora": "^5.4.1", - "semver": "^6.3.0", + "semver": "^7.5.2", "shell-quote": "^1.7.3" } }, @@ -7571,14 +8165,6 @@ "node": ">=8" } }, - "node_modules/@react-native-community/cli-tools/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@react-native-community/cli-tools/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7591,9 +8177,9 @@ } }, "node_modules/@react-native-community/cli-types": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-11.3.5.tgz", - "integrity": "sha512-pf0kdWMEfPSV/+8rcViDCFzbLMtWIHMZ8ay7hKwqaoWegsJ0oprSF2tSTH+LSC/7X1Beb9ssIvHj1m5C4es5Xg==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-11.3.6.tgz", + "integrity": "sha512-6DxjrMKx5x68N/tCJYVYRKAtlRHbtUVBZrnAvkxbRWFD9v4vhNgsPM0RQm8i2vRugeksnao5mbnRGpS6c0awCw==", "dependencies": { "joi": "^17.2.1" } @@ -7736,14 +8322,6 @@ "node": ">=8" } }, - "node_modules/@react-native-community/cli/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@react-native-community/cli/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7990,15 +8568,15 @@ "license": "MIT" }, "node_modules/@react-native/metro-config": { - "version": "0.72.9", - "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.72.9.tgz", - "integrity": "sha512-5MGmyDnXPeprRuvgPGE4LZ+e+ovofSd5YY6nFDwg6wbjRGOkeCRRlaTlQT+fjmv+zr4vYG+MUTKBlaO+fui/vA==", + "version": "0.72.11", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.72.11.tgz", + "integrity": "sha512-661EyQnDdVelyc0qP/ew7kKkGAh6N6KlkuPLC2SQ8sxaXskVU6fSuNlpLW4bUTBUDFKG8gEOU2hp6rzk4wQnGQ==", "dev": true, "dependencies": { "@react-native/js-polyfills": "^0.72.1", - "metro-config": "0.76.7", - "metro-react-native-babel-transformer": "0.76.7", - "metro-runtime": "0.76.7" + "metro-config": "0.76.8", + "metro-react-native-babel-transformer": "0.76.8", + "metro-runtime": "0.76.8" } }, "node_modules/@react-native/normalize-color": { @@ -8011,9 +8589,9 @@ "integrity": "sha512-285lfdqSXaqKuBbbtP9qL2tDrfxdOFtIMvkKadtleRQkdOxx+uzGvFr82KHmc/sSiMtfXGp7JnFYWVh4sFl7Yw==" }, "node_modules/@react-native/virtualized-lists": { - "version": "0.72.6", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.72.6.tgz", - "integrity": "sha512-JhT6ydu35LvbSKdwnhWDuGHMOwM0WAh9oza/X8vXHA8ELHRyQ/4p8eKz/bTQcbQziJaaleUURToGhFuCtgiMoA==", + "version": "0.72.8", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.72.8.tgz", + "integrity": "sha512-J3Q4Bkuo99k7mu+jPS9gSUSgq+lLRSI/+ahXNwV92XgJ/8UgOTxu2LPwhJnBk/sQKxq7E8WkZBnBiozukQMqrw==", "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" @@ -21038,7 +21616,8 @@ }, "node_modules/babel-plugin-syntax-trailing-function-commas": { "version": "7.0.0-beta.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", + "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==" }, "node_modules/babel-plugin-transform-class-properties": { "version": "6.24.1", @@ -21104,7 +21683,8 @@ }, "node_modules/babel-preset-fbjs": { "version": "3.4.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", + "integrity": "sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==", "dependencies": { "@babel/plugin-proposal-class-properties": "^7.0.0", "@babel/plugin-proposal-object-rest-spread": "^7.0.0", @@ -22669,9 +23249,9 @@ } }, "node_modules/cli-spinners": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", - "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", "engines": { "node": ">=6" }, @@ -23138,7 +23718,8 @@ }, "node_modules/connect": { "version": "3.7.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", @@ -23159,14 +23740,16 @@ }, "node_modules/connect/node_modules/debug": { "version": "2.6.9", - "license": "MIT", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { "ms": "2.0.0" } }, "node_modules/connect/node_modules/finalhandler": { "version": "1.1.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -23182,11 +23765,13 @@ }, "node_modules/connect/node_modules/ms": { "version": "2.0.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/connect/node_modules/on-finished": { "version": "2.3.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "dependencies": { "ee-first": "1.1.1" }, @@ -23196,7 +23781,8 @@ }, "node_modules/connect/node_modules/statuses": { "version": "1.5.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "engines": { "node": ">= 0.6" } @@ -24572,7 +25158,8 @@ }, "node_modules/denodeify": { "version": "1.2.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==" }, "node_modules/depd": { "version": "2.0.0", @@ -33572,9 +34159,9 @@ } }, "node_modules/joi": { - "version": "17.9.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.9.2.tgz", - "integrity": "sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==", + "version": "17.10.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.10.1.tgz", + "integrity": "sha512-vIiDxQKmRidUVp8KngT8MZSOcmRVm2zV7jbMjNYWuHcJWI0bUck3nRTGQjhpPlQenIQIBC5Vp9AhcnHbWQqafw==", "dependencies": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -34234,7 +34821,8 @@ }, "node_modules/lodash.throttle": { "version": "4.1.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" }, "node_modules/lodash.truncate": { "version": "4.4.2", @@ -35621,9 +36209,10 @@ } }, "node_modules/metro": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.76.7.tgz", - "integrity": "sha512-67ZGwDeumEPnrHI+pEDSKH2cx+C81Gx8Mn5qOtmGUPm/Up9Y4I1H2dJZ5n17MWzejNo0XAvPh0QL0CrlJEODVQ==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.76.8.tgz", + "integrity": "sha512-oQA3gLzrrYv3qKtuWArMgHPbHu8odZOD9AoavrqSFllkPgOtmkBvNNDLCELqv5SjBfqjISNffypg+5UGG3y0pg==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "@babel/core": "^7.20.0", @@ -35647,22 +36236,22 @@ "jest-worker": "^27.2.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.76.7", - "metro-cache": "0.76.7", - "metro-cache-key": "0.76.7", - "metro-config": "0.76.7", - "metro-core": "0.76.7", - "metro-file-map": "0.76.7", - "metro-inspector-proxy": "0.76.7", - "metro-minify-terser": "0.76.7", - "metro-minify-uglify": "0.76.7", - "metro-react-native-babel-preset": "0.76.7", - "metro-resolver": "0.76.7", - "metro-runtime": "0.76.7", - "metro-source-map": "0.76.7", - "metro-symbolicate": "0.76.7", - "metro-transform-plugins": "0.76.7", - "metro-transform-worker": "0.76.7", + "metro-babel-transformer": "0.76.8", + "metro-cache": "0.76.8", + "metro-cache-key": "0.76.8", + "metro-config": "0.76.8", + "metro-core": "0.76.8", + "metro-file-map": "0.76.8", + "metro-inspector-proxy": "0.76.8", + "metro-minify-terser": "0.76.8", + "metro-minify-uglify": "0.76.8", + "metro-react-native-babel-preset": "0.76.8", + "metro-resolver": "0.76.8", + "metro-runtime": "0.76.8", + "metro-source-map": "0.76.8", + "metro-symbolicate": "0.76.8", + "metro-transform-plugins": "0.76.8", + "metro-transform-worker": "0.76.8", "mime-types": "^2.1.27", "node-fetch": "^2.2.0", "nullthrows": "^1.1.1", @@ -35682,9 +36271,10 @@ } }, "node_modules/metro-babel-transformer": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.76.7.tgz", - "integrity": "sha512-bgr2OFn0J4r0qoZcHrwEvccF7g9k3wdgTOgk6gmGHrtlZ1Jn3oCpklW/DfZ9PzHfjY2mQammKTc19g/EFGyOJw==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.76.8.tgz", + "integrity": "sha512-Hh6PW34Ug/nShlBGxkwQJSgPGAzSJ9FwQXhUImkzdsDgVu6zj5bx258J8cJVSandjNoQ8nbaHK6CaHlnbZKbyA==", + "dev": true, "dependencies": { "@babel/core": "^7.20.0", "hermes-parser": "0.12.0", @@ -35695,11 +36285,12 @@ } }, "node_modules/metro-cache": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.76.7.tgz", - "integrity": "sha512-nWBMztrs5RuSxZRI7hgFgob5PhYDmxICh9FF8anm9/ito0u0vpPvRxt7sRu8fyeD2AHdXqE7kX32rWY0LiXgeg==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.76.8.tgz", + "integrity": "sha512-QBJSJIVNH7Hc/Yo6br/U/qQDUpiUdRgZ2ZBJmvAbmAKp2XDzsapnMwK/3BGj8JNWJF7OLrqrYHsRsukSbUBpvQ==", + "dev": true, "dependencies": { - "metro-core": "0.76.7", + "metro-core": "0.76.8", "rimraf": "^3.0.2" }, "engines": { @@ -35707,25 +36298,27 @@ } }, "node_modules/metro-cache-key": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.76.7.tgz", - "integrity": "sha512-0pecoIzwsD/Whn/Qfa+SDMX2YyasV0ndbcgUFx7w1Ct2sLHClujdhQ4ik6mvQmsaOcnGkIyN0zcceMDjC2+BFQ==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.76.8.tgz", + "integrity": "sha512-buKQ5xentPig9G6T37Ww/R/bC+/V1MA5xU/D8zjnhlelsrPG6w6LtHUS61ID3zZcMZqYaELWk5UIadIdDsaaLw==", + "dev": true, "engines": { "node": ">=16" } }, "node_modules/metro-config": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.76.7.tgz", - "integrity": "sha512-CFDyNb9bqxZemiChC/gNdXZ7OQkIwmXzkrEXivcXGbgzlt/b2juCv555GWJHyZSlorwnwJfY3uzAFu4A9iRVfg==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.76.8.tgz", + "integrity": "sha512-SL1lfKB0qGHALcAk2zBqVgQZpazDYvYFGwCK1ikz0S6Y/CM2i2/HwuZN31kpX6z3mqjv/6KvlzaKoTb1otuSAA==", + "dev": true, "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "jest-validate": "^29.2.1", - "metro": "0.76.7", - "metro-cache": "0.76.7", - "metro-core": "0.76.7", - "metro-runtime": "0.76.7" + "metro": "0.76.8", + "metro-cache": "0.76.8", + "metro-core": "0.76.8", + "metro-runtime": "0.76.8" }, "engines": { "node": ">=16" @@ -35735,6 +36328,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, "dependencies": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", @@ -35749,6 +36343,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, "dependencies": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" @@ -35761,6 +36356,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -35773,26 +36369,29 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, "engines": { "node": ">=4" } }, "node_modules/metro-core": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.76.7.tgz", - "integrity": "sha512-0b8KfrwPmwCMW+1V7ZQPkTy2tsEKZjYG9Pu1PTsu463Z9fxX7WaR0fcHFshv+J1CnQSUTwIGGjbNvj1teKe+pw==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.76.8.tgz", + "integrity": "sha512-sl2QLFI3d1b1XUUGxwzw/KbaXXU/bvFYrSKz6Sg19AdYGWFyzsgZ1VISRIDf+HWm4R/TJXluhWMEkEtZuqi3qA==", + "dev": true, "dependencies": { "lodash.throttle": "^4.1.1", - "metro-resolver": "0.76.7" + "metro-resolver": "0.76.8" }, "engines": { "node": ">=16" } }, "node_modules/metro-file-map": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.76.7.tgz", - "integrity": "sha512-s+zEkTcJ4mOJTgEE2ht4jIo1DZfeWreQR3tpT3gDV/Y/0UQ8aJBTv62dE775z0GLsWZApiblAYZsj7ZE8P06nw==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.76.8.tgz", + "integrity": "sha512-A/xP1YNEVwO1SUV9/YYo6/Y1MmzhL4ZnVgcJC3VmHp/BYVOXVStzgVbWv2wILe56IIMkfXU+jpXrGKKYhFyHVw==", + "dev": true, "dependencies": { "anymatch": "^3.0.3", "debug": "^2.2.0", @@ -35818,6 +36417,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -35833,6 +36433,7 @@ "version": "16.0.5", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "dev": true, "dependencies": { "@types/yargs-parser": "*" } @@ -35841,6 +36442,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -35855,6 +36457,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -35870,6 +36473,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -35880,12 +36484,14 @@ "node_modules/metro-file-map/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/metro-file-map/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "dependencies": { "ms": "2.0.0" } @@ -35894,6 +36500,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -35902,6 +36509,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "dev": true, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } @@ -35910,6 +36518,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dev": true, "dependencies": { "@jest/types": "^27.5.1", "@types/node": "*", @@ -35926,6 +36535,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -35939,6 +36549,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -35952,12 +36563,14 @@ "node_modules/metro-file-map/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true }, "node_modules/metro-file-map/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -35966,9 +36579,10 @@ } }, "node_modules/metro-inspector-proxy": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.76.7.tgz", - "integrity": "sha512-rNZ/6edTl/1qUekAhAbaFjczMphM50/UjtxiKulo6vqvgn/Mjd9hVqDvVYfAMZXqPvlusD88n38UjVYPkruLSg==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.76.8.tgz", + "integrity": "sha512-Us5o5UEd4Smgn1+TfHX4LvVPoWVo9VsVMn4Ldbk0g5CQx3Gu0ygc/ei2AKPGTwsOZmKxJeACj7yMH2kgxQP/iw==", + "dev": true, "dependencies": { "connect": "^3.6.5", "debug": "^2.2.0", @@ -35987,6 +36601,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -36000,6 +36615,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "dependencies": { "ms": "2.0.0" } @@ -36007,12 +36623,14 @@ "node_modules/metro-inspector-proxy/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true }, "node_modules/metro-inspector-proxy/node_modules/ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, "engines": { "node": ">=8.3.0" }, @@ -36033,6 +36651,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "engines": { "node": ">=10" } @@ -36041,6 +36660,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -36058,14 +36678,16 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "engines": { "node": ">=12" } }, "node_modules/metro-minify-terser": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.76.7.tgz", - "integrity": "sha512-FQiZGhIxCzhDwK4LxyPMLlq0Tsmla10X7BfNGlYFK0A5IsaVKNJbETyTzhpIwc+YFRT4GkFFwgo0V2N5vxO5HA==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.76.8.tgz", + "integrity": "sha512-Orbvg18qXHCrSj1KbaeSDVYRy/gkro2PC7Fy2tDSH1c9RB4aH8tuMOIXnKJE+1SXxBtjWmQ5Yirwkth2DyyEZA==", + "dev": true, "dependencies": { "terser": "^5.15.0" }, @@ -36074,9 +36696,10 @@ } }, "node_modules/metro-minify-uglify": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.76.7.tgz", - "integrity": "sha512-FuXIU3j2uNcSvQtPrAJjYWHruPiQ+EpE++J9Z+VznQKEHcIxMMoQZAfIF2IpZSrZYfLOjVFyGMvj41jQMxV1Vw==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.76.8.tgz", + "integrity": "sha512-6l8/bEvtVaTSuhG1FqS0+Mc8lZ3Bl4RI8SeRIifVLC21eeSDp4CEBUWSGjpFyUDfi6R5dXzYaFnSgMNyfxADiQ==", + "dev": true, "dependencies": { "uglify-es": "^3.1.9" }, @@ -36085,9 +36708,10 @@ } }, "node_modules/metro-react-native-babel-preset": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.7.tgz", - "integrity": "sha512-R25wq+VOSorAK3hc07NW0SmN8z9S/IR0Us0oGAsBcMZnsgkbOxu77Mduqf+f4is/wnWHc5+9bfiqdLnaMngiVw==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.8.tgz", + "integrity": "sha512-Ptza08GgqzxEdK8apYsjTx2S8WDUlS2ilBlu9DR1CUcHmg4g3kOkFylZroogVAUKtpYQNYwAvdsjmrSdDNtiAg==", + "dev": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/plugin-proposal-async-generator-functions": "^7.0.0", @@ -36138,20 +36762,22 @@ }, "node_modules/metro-react-native-babel-preset/node_modules/react-refresh": { "version": "0.4.3", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/metro-react-native-babel-transformer": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.76.7.tgz", - "integrity": "sha512-W6lW3J7y/05ph3c2p3KKJNhH0IdyxdOCbQ5it7aM2MAl0SM4wgKjaV6EYv9b3rHklpV6K3qMH37UKVcjMooWiA==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.76.8.tgz", + "integrity": "sha512-3h+LfS1WG1PAzhq8QF0kfXjxuXetbY/lgz8vYMQhgrMMp17WM1DNJD0gjx8tOGYbpbBC1qesJ45KMS4o5TA73A==", + "dev": true, "dependencies": { "@babel/core": "^7.20.0", "babel-preset-fbjs": "^3.4.0", "hermes-parser": "0.12.0", - "metro-react-native-babel-preset": "0.76.7", + "metro-react-native-babel-preset": "0.76.8", "nullthrows": "^1.1.1" }, "engines": { @@ -36162,17 +36788,18 @@ } }, "node_modules/metro-resolver": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.76.7.tgz", - "integrity": "sha512-pC0Wgq29HHIHrwz23xxiNgylhI8Rq1V01kQaJ9Kz11zWrIdlrH0ZdnJ7GC6qA0ErROG+cXmJ0rJb8/SW1Zp2IA==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.76.8.tgz", + "integrity": "sha512-KccOqc10vrzS7ZhG2NSnL2dh3uVydarB7nOhjreQ7C4zyWuiW9XpLC4h47KtGQv3Rnv/NDLJYeDqaJ4/+140HQ==", + "dev": true, "engines": { "node": ">=16" } }, "node_modules/metro-runtime": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.76.7.tgz", - "integrity": "sha512-MuWHubQHymUWBpZLwuKZQgA/qbb35WnDAKPo83rk7JRLIFPvzXSvFaC18voPuzJBt1V98lKQIonh6MiC9gd8Ug==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.76.8.tgz", + "integrity": "sha512-XKahvB+iuYJSCr3QqCpROli4B4zASAYpkK+j3a0CJmokxCDNbgyI4Fp88uIL6rNaZfN0Mv35S0b99SdFXIfHjg==", "dependencies": { "@babel/runtime": "^7.0.0", "react-refresh": "^0.4.0" @@ -36190,16 +36817,16 @@ } }, "node_modules/metro-source-map": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.76.7.tgz", - "integrity": "sha512-Prhx7PeRV1LuogT0Kn5VjCuFu9fVD68eefntdWabrksmNY6mXK8pRqzvNJOhTojh6nek+RxBzZeD6MIOOyXS6w==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.76.8.tgz", + "integrity": "sha512-Hh0ncPsHPVf6wXQSqJqB3K9Zbudht4aUtNpNXYXSxH+pteWqGAXnjtPsRAnCsCWl38wL0jYF0rJDdMajUI3BDw==", "dependencies": { "@babel/traverse": "^7.20.0", "@babel/types": "^7.20.0", "invariant": "^2.2.4", - "metro-symbolicate": "0.76.7", + "metro-symbolicate": "0.76.8", "nullthrows": "^1.1.1", - "ob1": "0.76.7", + "ob1": "0.76.8", "source-map": "^0.5.6", "vlq": "^1.0.0" }, @@ -36216,12 +36843,12 @@ } }, "node_modules/metro-symbolicate": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.76.7.tgz", - "integrity": "sha512-p0zWEME5qLSL1bJb93iq+zt5fz3sfVn9xFYzca1TJIpY5MommEaS64Va87lp56O0sfEIvh4307Oaf/ZzRjuLiQ==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.76.8.tgz", + "integrity": "sha512-LrRL3uy2VkzrIXVlxoPtqb40J6Bf1mlPNmUQewipc3qfKKFgtPHBackqDy1YL0njDsWopCKcfGtFYLn0PTUn3w==", "dependencies": { "invariant": "^2.2.4", - "metro-source-map": "0.76.7", + "metro-source-map": "0.76.8", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "through2": "^2.0.1", @@ -36243,9 +36870,10 @@ } }, "node_modules/metro-transform-plugins": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.76.7.tgz", - "integrity": "sha512-iSmnjVApbdivjuzb88Orb0JHvcEt5veVyFAzxiS5h0QB+zV79w6JCSqZlHCrbNOkOKBED//LqtKbFVakxllnNg==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.76.8.tgz", + "integrity": "sha512-PlkGTQNqS51Bx4vuufSQCdSn2R2rt7korzngo+b5GCkeX5pjinPjnO2kNhQ8l+5bO0iUD/WZ9nsM2PGGKIkWFA==", + "dev": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", @@ -36258,21 +36886,22 @@ } }, "node_modules/metro-transform-worker": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.76.7.tgz", - "integrity": "sha512-cGvELqFMVk9XTC15CMVzrCzcO6sO1lURfcbgjuuPdzaWuD11eEyocvkTX0DPiRjsvgAmicz4XYxVzgYl3MykDw==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.76.8.tgz", + "integrity": "sha512-mE1fxVAnJKmwwJyDtThildxxos9+DGs9+vTrx2ktSFMEVTtXS/bIv2W6hux1pqivqAfyJpTeACXHk5u2DgGvIQ==", + "dev": true, "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", "@babel/parser": "^7.20.0", "@babel/types": "^7.20.0", "babel-preset-fbjs": "^3.4.0", - "metro": "0.76.7", - "metro-babel-transformer": "0.76.7", - "metro-cache": "0.76.7", - "metro-cache-key": "0.76.7", - "metro-source-map": "0.76.7", - "metro-transform-plugins": "0.76.7", + "metro": "0.76.8", + "metro-babel-transformer": "0.76.8", + "metro-cache": "0.76.8", + "metro-cache-key": "0.76.8", + "metro-source-map": "0.76.8", + "metro-transform-plugins": "0.76.8", "nullthrows": "^1.1.1" }, "engines": { @@ -36283,6 +36912,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -36297,6 +36927,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -36311,12 +36942,14 @@ "node_modules/metro/node_modules/ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true }, "node_modules/metro/node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -36330,6 +36963,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -36340,12 +36974,14 @@ "node_modules/metro/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/metro/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "dependencies": { "ms": "2.0.0" } @@ -36354,6 +36990,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -36362,6 +36999,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -36375,6 +37013,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -36388,12 +37027,14 @@ "node_modules/metro/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true }, "node_modules/metro/node_modules/serialize-error": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -36402,6 +37043,7 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -36410,6 +37052,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -36421,6 +37064,7 @@ "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, "engines": { "node": ">=8.3.0" }, @@ -36441,6 +37085,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "engines": { "node": ">=10" } @@ -36449,6 +37094,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -36466,6 +37112,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "engines": { "node": ">=12" } @@ -38008,9 +38655,9 @@ "license": "MIT" }, "node_modules/ob1": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.76.7.tgz", - "integrity": "sha512-BQdRtxxoUNfSoZxqeBGOyuT9nEYSn18xZHwGMb0mMVpn2NBcYbnyKY4BK2LIHRgw33CBGlUmE+KMaNvyTpLLtQ==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.76.8.tgz", + "integrity": "sha512-dlBkJJV5M/msj9KYA9upc+nUWVwuOFFTbu28X6kZeGwcuW+JxaHSBZ70SYQnk5M+j5JbNLR6yKHmgW4M5E7X5g==", "engines": { "node": ">=16" } @@ -40246,20 +40893,20 @@ } }, "node_modules/react-native": { - "version": "0.72.3", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.72.3.tgz", - "integrity": "sha512-QqISi+JVmCssNP2FlQ4MWhlc4O/I00MRE1/GClvyZ8h/6kdsyk/sOirkYdZqX3+DrJfI3q+OnyMnsyaXIQ/5tQ==", + "version": "0.72.4", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.72.4.tgz", + "integrity": "sha512-+vrObi0wZR+NeqL09KihAAdVlQ9IdplwznJWtYrjnQ4UbCW6rkzZJebRsugwUneSOKNFaHFEo1uKU89HsgtYBg==", "dependencies": { "@jest/create-cache-key-function": "^29.2.1", - "@react-native-community/cli": "11.3.5", - "@react-native-community/cli-platform-android": "11.3.5", - "@react-native-community/cli-platform-ios": "11.3.5", + "@react-native-community/cli": "11.3.6", + "@react-native-community/cli-platform-android": "11.3.6", + "@react-native-community/cli-platform-ios": "11.3.6", "@react-native/assets-registry": "^0.72.0", "@react-native/codegen": "^0.72.6", "@react-native/gradle-plugin": "^0.72.11", "@react-native/js-polyfills": "^0.72.1", "@react-native/normalize-colors": "^0.72.0", - "@react-native/virtualized-lists": "^0.72.6", + "@react-native/virtualized-lists": "^0.72.8", "abort-controller": "^3.0.0", "anser": "^1.4.9", "base64-js": "^1.1.2", @@ -40270,8 +40917,8 @@ "jest-environment-node": "^29.2.1", "jsc-android": "^250231.0.0", "memoize-one": "^5.0.0", - "metro-runtime": "0.76.7", - "metro-source-map": "0.76.7", + "metro-runtime": "0.76.8", + "metro-source-map": "0.76.8", "mkdirp": "^0.5.1", "nullthrows": "^1.1.1", "pretty-format": "^26.5.2", @@ -40451,8 +41098,8 @@ }, "node_modules/react-native-google-places-autocomplete": { "version": "2.5.1", - "resolved": "git+ssh://git@github.com/Expensify/react-native-google-places-autocomplete.git#fd212e1e93cad72e97efad03893bea6d074d3e07", - "integrity": "sha512-tuU9AIvDtbGe+zEIdFXEuoMQl3n+JHWvMpTpMDdih1evgMiiX4tZxnHmMPpvhrxs9DiPaT/1+92eqJtMI3s+fA==", + "resolved": "git+ssh://git@github.com/Expensify/react-native-google-places-autocomplete.git#cef3ac29d9501091453136e1219e24c4ec9f9d76", + "integrity": "sha512-2z3ED8jOXasPTzBqvPwpG10LQsBArTRsYszmoz+TfqbgZrSBmP3c8rhaC//lx6Pvfs2r+KYWqJUrLf4mbCrjZw==", "license": "MIT", "dependencies": { "lodash.debounce": "^4.0.8", @@ -45010,7 +45657,8 @@ }, "node_modules/throat": { "version": "5.0.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" }, "node_modules/throttle-debounce": { "version": "3.0.1", @@ -46381,7 +47029,8 @@ }, "node_modules/vlq": { "version": "1.0.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==" }, "node_modules/vm-browserify": { "version": "1.1.2", @@ -52505,19 +53154,19 @@ "requires": {} }, "@react-native-community/cli": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-11.3.5.tgz", - "integrity": "sha512-wMXgKEWe6uesw7vyXKKjx5EDRog0QdXHxdgRguG14AjQRao1+4gXEWq2yyExOTi/GDY6dfJBUGTCwGQxhnk/Lg==", - "requires": { - "@react-native-community/cli-clean": "11.3.5", - "@react-native-community/cli-config": "11.3.5", - "@react-native-community/cli-debugger-ui": "11.3.5", - "@react-native-community/cli-doctor": "11.3.5", - "@react-native-community/cli-hermes": "11.3.5", - "@react-native-community/cli-plugin-metro": "11.3.5", - "@react-native-community/cli-server-api": "11.3.5", - "@react-native-community/cli-tools": "11.3.5", - "@react-native-community/cli-types": "11.3.5", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-11.3.6.tgz", + "integrity": "sha512-bdwOIYTBVQ9VK34dsf6t3u6vOUU5lfdhKaAxiAVArjsr7Je88Bgs4sAbsOYsNK3tkE8G77U6wLpekknXcanlww==", + "requires": { + "@react-native-community/cli-clean": "11.3.6", + "@react-native-community/cli-config": "11.3.6", + "@react-native-community/cli-debugger-ui": "11.3.6", + "@react-native-community/cli-doctor": "11.3.6", + "@react-native-community/cli-hermes": "11.3.6", + "@react-native-community/cli-plugin-metro": "11.3.6", + "@react-native-community/cli-server-api": "11.3.6", + "@react-native-community/cli-tools": "11.3.6", + "@react-native-community/cli-types": "11.3.6", "chalk": "^4.1.2", "commander": "^9.4.1", "execa": "^5.0.0", @@ -52525,7 +53174,7 @@ "fs-extra": "^8.1.0", "graceful-fs": "^4.1.3", "prompts": "^2.4.0", - "semver": "^6.3.0" + "semver": "^7.5.2" }, "dependencies": { "ansi-styles": { @@ -52624,11 +53273,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -52645,11 +53289,11 @@ } }, "@react-native-community/cli-clean": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-11.3.5.tgz", - "integrity": "sha512-1+7BU962wKkIkHRp/uW3jYbQKKGtU7L+R3g59D8K6uLccuxJYUBJv18753ojMa6SD3SAq5Xh31bAre+YwVcOTA==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-11.3.6.tgz", + "integrity": "sha512-jOOaeG5ebSXTHweq1NznVJVAFKtTFWL4lWgUXl845bCGX7t1lL8xQNWHKwT8Oh1pGR2CI3cKmRjY4hBg+pEI9g==", "requires": { - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "execa": "^5.0.0", "prompts": "^2.4.0" @@ -52701,11 +53345,11 @@ } }, "@react-native-community/cli-config": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-11.3.5.tgz", - "integrity": "sha512-fMblIsHlUleKfGsgWyjFJYfx1SqrsnhS/QXfA8w7iT6GrNOOjBp5UWx8+xlMDFcmOb9e42g1ExFDKl3n8FWkxQ==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-11.3.6.tgz", + "integrity": "sha512-edy7fwllSFLan/6BG6/rznOBCLPrjmJAE10FzkEqNLHowi0bckiAPg1+1jlgQ2qqAxV5kuk+c9eajVfQvPLYDA==", "requires": { - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "cosmiconfig": "^5.1.0", "deepmerge": "^4.3.0", @@ -52793,22 +53437,22 @@ } }, "@react-native-community/cli-debugger-ui": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-11.3.5.tgz", - "integrity": "sha512-o5JVCKEpPUXMX4r3p1cYjiy3FgdOEkezZcQ6owWEae2dYvV19lLYyJwnocm9Y7aG9PvpgI3PIMVh3KZbhS21eA==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-11.3.6.tgz", + "integrity": "sha512-jhMOSN/iOlid9jn/A2/uf7HbC3u7+lGktpeGSLnHNw21iahFBzcpuO71ekEdlmTZ4zC/WyxBXw9j2ka33T358w==", "requires": { "serve-static": "^1.13.1" } }, "@react-native-community/cli-doctor": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-11.3.5.tgz", - "integrity": "sha512-+4BuFHjoV4FFjX5y60l0s6nS0agidb1izTVwsFixeFKW73LUkOLu+Ae5HI94RAFEPE4ePEVNgYX3FynIau6K0g==", - "requires": { - "@react-native-community/cli-config": "11.3.5", - "@react-native-community/cli-platform-android": "11.3.5", - "@react-native-community/cli-platform-ios": "11.3.5", - "@react-native-community/cli-tools": "11.3.5", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-11.3.6.tgz", + "integrity": "sha512-UT/Tt6omVPi1j6JEX+CObc85eVFghSZwy4GR9JFMsO7gNg2Tvcu1RGWlUkrbmWMAMHw127LUu6TGK66Ugu1NLA==", + "requires": { + "@react-native-community/cli-config": "11.3.6", + "@react-native-community/cli-platform-android": "11.3.6", + "@react-native-community/cli-platform-ios": "11.3.6", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "command-exists": "^1.2.8", "envinfo": "^7.7.2", @@ -52818,7 +53462,7 @@ "node-stream-zip": "^1.9.1", "ora": "^5.4.1", "prompts": "^2.4.0", - "semver": "^6.3.0", + "semver": "^7.5.2", "strip-ansi": "^5.2.0", "sudo-prompt": "^9.0.0", "wcwidth": "^1.0.1", @@ -52865,11 +53509,6 @@ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -52889,12 +53528,12 @@ } }, "@react-native-community/cli-hermes": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-11.3.5.tgz", - "integrity": "sha512-+3m34hiaJpFel8BlJE7kJOaPzWR/8U8APZG2LXojbAdBAg99EGmQcwXIgsSVJFvH8h/nezf4DHbsPKigIe33zA==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-11.3.6.tgz", + "integrity": "sha512-O55YAYGZ3XynpUdePPVvNuUPGPY0IJdctLAOHme73OvS80gNwfntHDXfmY70TGHWIfkK2zBhA0B+2v8s5aTyTA==", "requires": { - "@react-native-community/cli-platform-android": "11.3.5", - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-platform-android": "11.3.6", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "hermes-profile-transformer": "^0.0.6", "ip": "^1.1.5" @@ -52951,11 +53590,11 @@ } }, "@react-native-community/cli-platform-android": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-11.3.5.tgz", - "integrity": "sha512-s4Lj7FKxJ/BofGi/ifjPfrA9MjFwIgYpHnHBSlqtbsvPoSYzmVCU2qlWM8fb3AmkXIwyYt4A6MEr3MmNT2UoBg==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-11.3.6.tgz", + "integrity": "sha512-ZARrpLv5tn3rmhZc//IuDM1LSAdYnjUmjrp58RynlvjLDI4ZEjBAGCQmgysRgXAsK7ekMrfkZgemUczfn9td2A==", "requires": { - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "execa": "^5.0.0", "glob": "^7.1.3", @@ -53008,11 +53647,11 @@ } }, "@react-native-community/cli-platform-ios": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-11.3.5.tgz", - "integrity": "sha512-ytJC/YCFD7P+KuQHOT5Jzh1ho2XbJEjq71yHa1gJP2PG/Q/uB4h1x2XpxDqv5iXU6E250yjvKMmkReKTW4CTig==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-11.3.6.tgz", + "integrity": "sha512-tZ9VbXWiRW+F+fbZzpLMZlj93g3Q96HpuMsS6DRhrTiG+vMQ3o6oPWSEEmMGOvJSYU7+y68Dc9ms2liC7VD6cw==", "requires": { - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "execa": "^5.0.0", "fast-xml-parser": "^4.0.12", @@ -53066,12 +53705,12 @@ } }, "@react-native-community/cli-plugin-metro": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-11.3.5.tgz", - "integrity": "sha512-r9AekfeLKdblB7LfWB71IrNy1XM03WrByQlUQajUOZAP2NmUUBLl9pMZscPjJeOSgLpHB9ixEFTIOhTabri/qg==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-11.3.6.tgz", + "integrity": "sha512-D97racrPX3069ibyabJNKw9aJpVcaZrkYiEzsEnx50uauQtPDoQ1ELb/5c6CtMhAEGKoZ0B5MS23BbsSZcLs2g==", "requires": { - "@react-native-community/cli-server-api": "11.3.5", - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-server-api": "11.3.6", + "@react-native-community/cli-tools": "11.3.6", "chalk": "^4.1.2", "execa": "^5.0.0", "metro": "0.76.7", @@ -53083,6 +53722,26 @@ "readline": "^1.3.0" }, "dependencies": { + "@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", + "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -53100,6 +53759,21 @@ "supports-color": "^7.1.0" } }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -53113,11 +53787,404 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "jest-regex-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==" + }, + "jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "requires": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==" + } + } + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "metro": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.76.7.tgz", + "integrity": "sha512-67ZGwDeumEPnrHI+pEDSKH2cx+C81Gx8Mn5qOtmGUPm/Up9Y4I1H2dJZ5n17MWzejNo0XAvPh0QL0CrlJEODVQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "accepts": "^1.3.7", + "async": "^3.2.2", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "denodeify": "^1.2.1", + "error-stack-parser": "^2.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.12.0", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^27.2.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.76.7", + "metro-cache": "0.76.7", + "metro-cache-key": "0.76.7", + "metro-config": "0.76.7", + "metro-core": "0.76.7", + "metro-file-map": "0.76.7", + "metro-inspector-proxy": "0.76.7", + "metro-minify-terser": "0.76.7", + "metro-minify-uglify": "0.76.7", + "metro-react-native-babel-preset": "0.76.7", + "metro-resolver": "0.76.7", + "metro-runtime": "0.76.7", + "metro-source-map": "0.76.7", + "metro-symbolicate": "0.76.7", + "metro-transform-plugins": "0.76.7", + "metro-transform-worker": "0.76.7", + "mime-types": "^2.1.27", + "node-fetch": "^2.2.0", + "nullthrows": "^1.1.1", + "rimraf": "^3.0.2", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "strip-ansi": "^6.0.0", + "throat": "^5.0.0", + "ws": "^7.5.1", + "yargs": "^17.6.2" + } + }, + "metro-babel-transformer": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.76.7.tgz", + "integrity": "sha512-bgr2OFn0J4r0qoZcHrwEvccF7g9k3wdgTOgk6gmGHrtlZ1Jn3oCpklW/DfZ9PzHfjY2mQammKTc19g/EFGyOJw==", + "requires": { + "@babel/core": "^7.20.0", + "hermes-parser": "0.12.0", + "nullthrows": "^1.1.1" + } + }, + "metro-cache": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.76.7.tgz", + "integrity": "sha512-nWBMztrs5RuSxZRI7hgFgob5PhYDmxICh9FF8anm9/ito0u0vpPvRxt7sRu8fyeD2AHdXqE7kX32rWY0LiXgeg==", + "requires": { + "metro-core": "0.76.7", + "rimraf": "^3.0.2" + } + }, + "metro-cache-key": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.76.7.tgz", + "integrity": "sha512-0pecoIzwsD/Whn/Qfa+SDMX2YyasV0ndbcgUFx7w1Ct2sLHClujdhQ4ik6mvQmsaOcnGkIyN0zcceMDjC2+BFQ==" + }, + "metro-config": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.76.7.tgz", + "integrity": "sha512-CFDyNb9bqxZemiChC/gNdXZ7OQkIwmXzkrEXivcXGbgzlt/b2juCv555GWJHyZSlorwnwJfY3uzAFu4A9iRVfg==", + "requires": { + "connect": "^3.6.5", + "cosmiconfig": "^5.0.5", + "jest-validate": "^29.2.1", + "metro": "0.76.7", + "metro-cache": "0.76.7", + "metro-core": "0.76.7", + "metro-runtime": "0.76.7" + } + }, + "metro-core": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.76.7.tgz", + "integrity": "sha512-0b8KfrwPmwCMW+1V7ZQPkTy2tsEKZjYG9Pu1PTsu463Z9fxX7WaR0fcHFshv+J1CnQSUTwIGGjbNvj1teKe+pw==", + "requires": { + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.76.7" + } + }, + "metro-file-map": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.76.7.tgz", + "integrity": "sha512-s+zEkTcJ4mOJTgEE2ht4jIo1DZfeWreQR3tpT3gDV/Y/0UQ8aJBTv62dE775z0GLsWZApiblAYZsj7ZE8P06nw==", + "requires": { + "anymatch": "^3.0.3", + "debug": "^2.2.0", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-regex-util": "^27.0.6", + "jest-util": "^27.2.0", + "jest-worker": "^27.2.0", + "micromatch": "^4.0.4", + "node-abort-controller": "^3.1.1", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + } + }, + "metro-inspector-proxy": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.76.7.tgz", + "integrity": "sha512-rNZ/6edTl/1qUekAhAbaFjczMphM50/UjtxiKulo6vqvgn/Mjd9hVqDvVYfAMZXqPvlusD88n38UjVYPkruLSg==", + "requires": { + "connect": "^3.6.5", + "debug": "^2.2.0", + "node-fetch": "^2.2.0", + "ws": "^7.5.1", + "yargs": "^17.6.2" + } + }, + "metro-minify-terser": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.76.7.tgz", + "integrity": "sha512-FQiZGhIxCzhDwK4LxyPMLlq0Tsmla10X7BfNGlYFK0A5IsaVKNJbETyTzhpIwc+YFRT4GkFFwgo0V2N5vxO5HA==", + "requires": { + "terser": "^5.15.0" + } + }, + "metro-minify-uglify": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.76.7.tgz", + "integrity": "sha512-FuXIU3j2uNcSvQtPrAJjYWHruPiQ+EpE++J9Z+VznQKEHcIxMMoQZAfIF2IpZSrZYfLOjVFyGMvj41jQMxV1Vw==", + "requires": { + "uglify-es": "^3.1.9" + } + }, + "metro-react-native-babel-preset": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.7.tgz", + "integrity": "sha512-R25wq+VOSorAK3hc07NW0SmN8z9S/IR0Us0oGAsBcMZnsgkbOxu77Mduqf+f4is/wnWHc5+9bfiqdLnaMngiVw==", + "requires": { + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.18.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.20.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.4.0" + } + }, + "metro-react-native-babel-transformer": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.76.7.tgz", + "integrity": "sha512-W6lW3J7y/05ph3c2p3KKJNhH0IdyxdOCbQ5it7aM2MAl0SM4wgKjaV6EYv9b3rHklpV6K3qMH37UKVcjMooWiA==", + "requires": { + "@babel/core": "^7.20.0", + "babel-preset-fbjs": "^3.4.0", + "hermes-parser": "0.12.0", + "metro-react-native-babel-preset": "0.76.7", + "nullthrows": "^1.1.1" + } + }, + "metro-resolver": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.76.7.tgz", + "integrity": "sha512-pC0Wgq29HHIHrwz23xxiNgylhI8Rq1V01kQaJ9Kz11zWrIdlrH0ZdnJ7GC6qA0ErROG+cXmJ0rJb8/SW1Zp2IA==" + }, + "metro-runtime": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.76.7.tgz", + "integrity": "sha512-MuWHubQHymUWBpZLwuKZQgA/qbb35WnDAKPo83rk7JRLIFPvzXSvFaC18voPuzJBt1V98lKQIonh6MiC9gd8Ug==", + "requires": { + "@babel/runtime": "^7.0.0", + "react-refresh": "^0.4.0" + } + }, + "metro-source-map": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.76.7.tgz", + "integrity": "sha512-Prhx7PeRV1LuogT0Kn5VjCuFu9fVD68eefntdWabrksmNY6mXK8pRqzvNJOhTojh6nek+RxBzZeD6MIOOyXS6w==", + "requires": { + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "invariant": "^2.2.4", + "metro-symbolicate": "0.76.7", + "nullthrows": "^1.1.1", + "ob1": "0.76.7", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + } + }, + "metro-symbolicate": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.76.7.tgz", + "integrity": "sha512-p0zWEME5qLSL1bJb93iq+zt5fz3sfVn9xFYzca1TJIpY5MommEaS64Va87lp56O0sfEIvh4307Oaf/ZzRjuLiQ==", + "requires": { + "invariant": "^2.2.4", + "metro-source-map": "0.76.7", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" + } + }, + "metro-transform-plugins": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.76.7.tgz", + "integrity": "sha512-iSmnjVApbdivjuzb88Orb0JHvcEt5veVyFAzxiS5h0QB+zV79w6JCSqZlHCrbNOkOKBED//LqtKbFVakxllnNg==", + "requires": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "nullthrows": "^1.1.1" + } + }, + "metro-transform-worker": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.76.7.tgz", + "integrity": "sha512-cGvELqFMVk9XTC15CMVzrCzcO6sO1lURfcbgjuuPdzaWuD11eEyocvkTX0DPiRjsvgAmicz4XYxVzgYl3MykDw==", + "requires": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/types": "^7.20.0", + "babel-preset-fbjs": "^3.4.0", + "metro": "0.76.7", + "metro-babel-transformer": "0.76.7", + "metro-cache": "0.76.7", + "metro-cache-key": "0.76.7", + "metro-source-map": "0.76.7", + "metro-transform-plugins": "0.76.7", + "nullthrows": "^1.1.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "ob1": { + "version": "0.76.7", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.76.7.tgz", + "integrity": "sha512-BQdRtxxoUNfSoZxqeBGOyuT9nEYSn18xZHwGMb0mMVpn2NBcYbnyKY4BK2LIHRgw33CBGlUmE+KMaNvyTpLLtQ==" + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "react-refresh": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", + "integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==" + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==" + }, + "serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -53125,16 +54192,46 @@ "requires": { "has-flag": "^4.0.0" } + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "requires": {} + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" } } }, "@react-native-community/cli-server-api": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-11.3.5.tgz", - "integrity": "sha512-PM/jF13uD1eAKuC84lntNuM5ZvJAtyb+H896P1dBIXa9boPLa3KejfUvNVoyOUJ5s8Ht25JKbc3yieV2+GMBDA==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-11.3.6.tgz", + "integrity": "sha512-8GUKodPnURGtJ9JKg8yOHIRtWepPciI3ssXVw5jik7+dZ43yN8P5BqCoDaq8e1H1yRer27iiOfT7XVnwk8Dueg==", "requires": { - "@react-native-community/cli-debugger-ui": "11.3.5", - "@react-native-community/cli-tools": "11.3.5", + "@react-native-community/cli-debugger-ui": "11.3.6", + "@react-native-community/cli-tools": "11.3.6", "compression": "^1.7.1", "connect": "^3.6.5", "errorhandler": "^1.5.1", @@ -53195,9 +54292,9 @@ } }, "@react-native-community/cli-tools": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-11.3.5.tgz", - "integrity": "sha512-zDklE1+ah/zL4BLxut5XbzqCj9KTHzbYBKX7//cXw2/0TpkNCaY9c+iKx//gZ5m7U1OKbb86Fm2b0AKtKVRf6Q==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-11.3.6.tgz", + "integrity": "sha512-JpmUTcDwAGiTzLsfMlIAYpCMSJ9w2Qlf7PU7mZIRyEu61UzEawyw83DkqfbzDPBuRwRnaeN44JX2CP/yTO3ThQ==", "requires": { "appdirsjs": "^1.2.4", "chalk": "^4.1.2", @@ -53206,7 +54303,7 @@ "node-fetch": "^2.6.0", "open": "^6.2.0", "ora": "^5.4.1", - "semver": "^6.3.0", + "semver": "^7.5.2", "shell-quote": "^1.7.3" }, "dependencies": { @@ -53258,11 +54355,6 @@ "is-wsl": "^1.1.0" } }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -53274,9 +54366,9 @@ } }, "@react-native-community/cli-types": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-11.3.5.tgz", - "integrity": "sha512-pf0kdWMEfPSV/+8rcViDCFzbLMtWIHMZ8ay7hKwqaoWegsJ0oprSF2tSTH+LSC/7X1Beb9ssIvHj1m5C4es5Xg==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-11.3.6.tgz", + "integrity": "sha512-6DxjrMKx5x68N/tCJYVYRKAtlRHbtUVBZrnAvkxbRWFD9v4vhNgsPM0RQm8i2vRugeksnao5mbnRGpS6c0awCw==", "requires": { "joi": "^17.2.1" } @@ -53420,15 +54512,15 @@ "version": "0.72.1" }, "@react-native/metro-config": { - "version": "0.72.9", - "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.72.9.tgz", - "integrity": "sha512-5MGmyDnXPeprRuvgPGE4LZ+e+ovofSd5YY6nFDwg6wbjRGOkeCRRlaTlQT+fjmv+zr4vYG+MUTKBlaO+fui/vA==", + "version": "0.72.11", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.72.11.tgz", + "integrity": "sha512-661EyQnDdVelyc0qP/ew7kKkGAh6N6KlkuPLC2SQ8sxaXskVU6fSuNlpLW4bUTBUDFKG8gEOU2hp6rzk4wQnGQ==", "dev": true, "requires": { "@react-native/js-polyfills": "^0.72.1", - "metro-config": "0.76.7", - "metro-react-native-babel-transformer": "0.76.7", - "metro-runtime": "0.76.7" + "metro-config": "0.76.8", + "metro-react-native-babel-transformer": "0.76.8", + "metro-runtime": "0.76.8" } }, "@react-native/normalize-color": { @@ -53440,9 +54532,9 @@ "integrity": "sha512-285lfdqSXaqKuBbbtP9qL2tDrfxdOFtIMvkKadtleRQkdOxx+uzGvFr82KHmc/sSiMtfXGp7JnFYWVh4sFl7Yw==" }, "@react-native/virtualized-lists": { - "version": "0.72.6", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.72.6.tgz", - "integrity": "sha512-JhT6ydu35LvbSKdwnhWDuGHMOwM0WAh9oza/X8vXHA8ELHRyQ/4p8eKz/bTQcbQziJaaleUURToGhFuCtgiMoA==", + "version": "0.72.8", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.72.8.tgz", + "integrity": "sha512-J3Q4Bkuo99k7mu+jPS9gSUSgq+lLRSI/+ahXNwV92XgJ/8UgOTxu2LPwhJnBk/sQKxq7E8WkZBnBiozukQMqrw==", "requires": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" @@ -62652,7 +63744,9 @@ "dev": true }, "babel-plugin-syntax-trailing-function-commas": { - "version": "7.0.0-beta.0" + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", + "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==" }, "babel-plugin-transform-class-properties": { "version": "6.24.1", @@ -62709,6 +63803,8 @@ }, "babel-preset-fbjs": { "version": "3.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", + "integrity": "sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==", "requires": { "@babel/plugin-proposal-class-properties": "^7.0.0", "@babel/plugin-proposal-object-rest-spread": "^7.0.0", @@ -63788,9 +64884,9 @@ } }, "cli-spinners": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", - "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==" + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==" }, "cli-table3": { "version": "0.6.3", @@ -64109,6 +65205,8 @@ }, "connect": { "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "requires": { "debug": "2.6.9", "finalhandler": "1.1.2", @@ -64118,12 +65216,16 @@ "dependencies": { "debug": { "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { "ms": "2.0.0" } }, "finalhandler": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -64135,16 +65237,22 @@ } }, "ms": { - "version": "2.0.0" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "on-finished": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "requires": { "ee-first": "1.1.1" } }, "statuses": { - "version": "1.5.0" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" } } }, @@ -65076,7 +66184,9 @@ "dev": true }, "denodeify": { - "version": "1.2.1" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==" }, "depd": { "version": "2.0.0" @@ -71176,9 +72286,9 @@ } }, "joi": { - "version": "17.9.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.9.2.tgz", - "integrity": "sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==", + "version": "17.10.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.10.1.tgz", + "integrity": "sha512-vIiDxQKmRidUVp8KngT8MZSOcmRVm2zV7jbMjNYWuHcJWI0bUck3nRTGQjhpPlQenIQIBC5Vp9AhcnHbWQqafw==", "requires": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -71636,7 +72746,9 @@ "dev": true }, "lodash.throttle": { - "version": "4.1.1" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" }, "lodash.truncate": { "version": "4.4.2", @@ -72640,9 +73752,10 @@ "dev": true }, "metro": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.76.7.tgz", - "integrity": "sha512-67ZGwDeumEPnrHI+pEDSKH2cx+C81Gx8Mn5qOtmGUPm/Up9Y4I1H2dJZ5n17MWzejNo0XAvPh0QL0CrlJEODVQ==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.76.8.tgz", + "integrity": "sha512-oQA3gLzrrYv3qKtuWArMgHPbHu8odZOD9AoavrqSFllkPgOtmkBvNNDLCELqv5SjBfqjISNffypg+5UGG3y0pg==", + "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/core": "^7.20.0", @@ -72666,22 +73779,22 @@ "jest-worker": "^27.2.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.76.7", - "metro-cache": "0.76.7", - "metro-cache-key": "0.76.7", - "metro-config": "0.76.7", - "metro-core": "0.76.7", - "metro-file-map": "0.76.7", - "metro-inspector-proxy": "0.76.7", - "metro-minify-terser": "0.76.7", - "metro-minify-uglify": "0.76.7", - "metro-react-native-babel-preset": "0.76.7", - "metro-resolver": "0.76.7", - "metro-runtime": "0.76.7", - "metro-source-map": "0.76.7", - "metro-symbolicate": "0.76.7", - "metro-transform-plugins": "0.76.7", - "metro-transform-worker": "0.76.7", + "metro-babel-transformer": "0.76.8", + "metro-cache": "0.76.8", + "metro-cache-key": "0.76.8", + "metro-config": "0.76.8", + "metro-core": "0.76.8", + "metro-file-map": "0.76.8", + "metro-inspector-proxy": "0.76.8", + "metro-minify-terser": "0.76.8", + "metro-minify-uglify": "0.76.8", + "metro-react-native-babel-preset": "0.76.8", + "metro-resolver": "0.76.8", + "metro-runtime": "0.76.8", + "metro-source-map": "0.76.8", + "metro-symbolicate": "0.76.8", + "metro-transform-plugins": "0.76.8", + "metro-transform-worker": "0.76.8", "mime-types": "^2.1.27", "node-fetch": "^2.2.0", "nullthrows": "^1.1.1", @@ -72698,6 +73811,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -72706,6 +73820,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -72714,12 +73829,14 @@ "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -72730,6 +73847,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -72737,12 +73855,14 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -72750,12 +73870,14 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -72766,6 +73888,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -72775,22 +73898,26 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true }, "serialize-error": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==" + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "dev": true }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -72799,17 +73926,20 @@ "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, "requires": {} }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "requires": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -72823,14 +73953,16 @@ "yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true } } }, "metro-babel-transformer": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.76.7.tgz", - "integrity": "sha512-bgr2OFn0J4r0qoZcHrwEvccF7g9k3wdgTOgk6gmGHrtlZ1Jn3oCpklW/DfZ9PzHfjY2mQammKTc19g/EFGyOJw==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.76.8.tgz", + "integrity": "sha512-Hh6PW34Ug/nShlBGxkwQJSgPGAzSJ9FwQXhUImkzdsDgVu6zj5bx258J8cJVSandjNoQ8nbaHK6CaHlnbZKbyA==", + "dev": true, "requires": { "@babel/core": "^7.20.0", "hermes-parser": "0.12.0", @@ -72838,37 +73970,41 @@ } }, "metro-cache": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.76.7.tgz", - "integrity": "sha512-nWBMztrs5RuSxZRI7hgFgob5PhYDmxICh9FF8anm9/ito0u0vpPvRxt7sRu8fyeD2AHdXqE7kX32rWY0LiXgeg==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.76.8.tgz", + "integrity": "sha512-QBJSJIVNH7Hc/Yo6br/U/qQDUpiUdRgZ2ZBJmvAbmAKp2XDzsapnMwK/3BGj8JNWJF7OLrqrYHsRsukSbUBpvQ==", + "dev": true, "requires": { - "metro-core": "0.76.7", + "metro-core": "0.76.8", "rimraf": "^3.0.2" } }, "metro-cache-key": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.76.7.tgz", - "integrity": "sha512-0pecoIzwsD/Whn/Qfa+SDMX2YyasV0ndbcgUFx7w1Ct2sLHClujdhQ4ik6mvQmsaOcnGkIyN0zcceMDjC2+BFQ==" + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.76.8.tgz", + "integrity": "sha512-buKQ5xentPig9G6T37Ww/R/bC+/V1MA5xU/D8zjnhlelsrPG6w6LtHUS61ID3zZcMZqYaELWk5UIadIdDsaaLw==", + "dev": true }, "metro-config": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.76.7.tgz", - "integrity": "sha512-CFDyNb9bqxZemiChC/gNdXZ7OQkIwmXzkrEXivcXGbgzlt/b2juCv555GWJHyZSlorwnwJfY3uzAFu4A9iRVfg==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.76.8.tgz", + "integrity": "sha512-SL1lfKB0qGHALcAk2zBqVgQZpazDYvYFGwCK1ikz0S6Y/CM2i2/HwuZN31kpX6z3mqjv/6KvlzaKoTb1otuSAA==", + "dev": true, "requires": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "jest-validate": "^29.2.1", - "metro": "0.76.7", - "metro-cache": "0.76.7", - "metro-core": "0.76.7", - "metro-runtime": "0.76.7" + "metro": "0.76.8", + "metro-cache": "0.76.8", + "metro-core": "0.76.8", + "metro-runtime": "0.76.8" }, "dependencies": { "cosmiconfig": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, "requires": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", @@ -72880,6 +74016,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, "requires": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" @@ -72889,6 +74026,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -72897,23 +74035,26 @@ "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==" + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true } } }, "metro-core": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.76.7.tgz", - "integrity": "sha512-0b8KfrwPmwCMW+1V7ZQPkTy2tsEKZjYG9Pu1PTsu463Z9fxX7WaR0fcHFshv+J1CnQSUTwIGGjbNvj1teKe+pw==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.76.8.tgz", + "integrity": "sha512-sl2QLFI3d1b1XUUGxwzw/KbaXXU/bvFYrSKz6Sg19AdYGWFyzsgZ1VISRIDf+HWm4R/TJXluhWMEkEtZuqi3qA==", + "dev": true, "requires": { "lodash.throttle": "^4.1.1", - "metro-resolver": "0.76.7" + "metro-resolver": "0.76.8" } }, "metro-file-map": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.76.7.tgz", - "integrity": "sha512-s+zEkTcJ4mOJTgEE2ht4jIo1DZfeWreQR3tpT3gDV/Y/0UQ8aJBTv62dE775z0GLsWZApiblAYZsj7ZE8P06nw==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.76.8.tgz", + "integrity": "sha512-A/xP1YNEVwO1SUV9/YYo6/Y1MmzhL4ZnVgcJC3VmHp/BYVOXVStzgVbWv2wILe56IIMkfXU+jpXrGKKYhFyHVw==", + "dev": true, "requires": { "anymatch": "^3.0.3", "debug": "^2.2.0", @@ -72934,6 +74075,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -72946,6 +74088,7 @@ "version": "16.0.5", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "dev": true, "requires": { "@types/yargs-parser": "*" } @@ -72954,6 +74097,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -72962,6 +74106,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -72971,6 +74116,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -72978,12 +74124,14 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -72991,17 +74139,20 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "jest-regex-util": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==" + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "dev": true }, "jest-util": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dev": true, "requires": { "@jest/types": "^27.5.1", "@types/node": "*", @@ -73015,6 +74166,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -73025,6 +74177,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -73034,12 +74187,14 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -73047,9 +74202,10 @@ } }, "metro-inspector-proxy": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.76.7.tgz", - "integrity": "sha512-rNZ/6edTl/1qUekAhAbaFjczMphM50/UjtxiKulo6vqvgn/Mjd9hVqDvVYfAMZXqPvlusD88n38UjVYPkruLSg==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.76.8.tgz", + "integrity": "sha512-Us5o5UEd4Smgn1+TfHX4LvVPoWVo9VsVMn4Ldbk0g5CQx3Gu0ygc/ei2AKPGTwsOZmKxJeACj7yMH2kgxQP/iw==", + "dev": true, "requires": { "connect": "^3.6.5", "debug": "^2.2.0", @@ -73062,6 +74218,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -73072,6 +74229,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -73079,23 +74237,27 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true }, "ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, "requires": {} }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "requires": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -73109,30 +74271,34 @@ "yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true } } }, "metro-minify-terser": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.76.7.tgz", - "integrity": "sha512-FQiZGhIxCzhDwK4LxyPMLlq0Tsmla10X7BfNGlYFK0A5IsaVKNJbETyTzhpIwc+YFRT4GkFFwgo0V2N5vxO5HA==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.76.8.tgz", + "integrity": "sha512-Orbvg18qXHCrSj1KbaeSDVYRy/gkro2PC7Fy2tDSH1c9RB4aH8tuMOIXnKJE+1SXxBtjWmQ5Yirwkth2DyyEZA==", + "dev": true, "requires": { "terser": "^5.15.0" } }, "metro-minify-uglify": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.76.7.tgz", - "integrity": "sha512-FuXIU3j2uNcSvQtPrAJjYWHruPiQ+EpE++J9Z+VznQKEHcIxMMoQZAfIF2IpZSrZYfLOjVFyGMvj41jQMxV1Vw==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.76.8.tgz", + "integrity": "sha512-6l8/bEvtVaTSuhG1FqS0+Mc8lZ3Bl4RI8SeRIifVLC21eeSDp4CEBUWSGjpFyUDfi6R5dXzYaFnSgMNyfxADiQ==", + "dev": true, "requires": { "uglify-es": "^3.1.9" } }, "metro-react-native-babel-preset": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.7.tgz", - "integrity": "sha512-R25wq+VOSorAK3hc07NW0SmN8z9S/IR0Us0oGAsBcMZnsgkbOxu77Mduqf+f4is/wnWHc5+9bfiqdLnaMngiVw==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.8.tgz", + "integrity": "sha512-Ptza08GgqzxEdK8apYsjTx2S8WDUlS2ilBlu9DR1CUcHmg4g3kOkFylZroogVAUKtpYQNYwAvdsjmrSdDNtiAg==", + "dev": true, "requires": { "@babel/core": "^7.20.0", "@babel/plugin-proposal-async-generator-functions": "^7.0.0", @@ -73176,31 +74342,34 @@ }, "dependencies": { "react-refresh": { - "version": "0.4.3" + "version": "0.4.3", + "dev": true } } }, "metro-react-native-babel-transformer": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.76.7.tgz", - "integrity": "sha512-W6lW3J7y/05ph3c2p3KKJNhH0IdyxdOCbQ5it7aM2MAl0SM4wgKjaV6EYv9b3rHklpV6K3qMH37UKVcjMooWiA==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.76.8.tgz", + "integrity": "sha512-3h+LfS1WG1PAzhq8QF0kfXjxuXetbY/lgz8vYMQhgrMMp17WM1DNJD0gjx8tOGYbpbBC1qesJ45KMS4o5TA73A==", + "dev": true, "requires": { "@babel/core": "^7.20.0", "babel-preset-fbjs": "^3.4.0", "hermes-parser": "0.12.0", - "metro-react-native-babel-preset": "0.76.7", + "metro-react-native-babel-preset": "0.76.8", "nullthrows": "^1.1.1" } }, "metro-resolver": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.76.7.tgz", - "integrity": "sha512-pC0Wgq29HHIHrwz23xxiNgylhI8Rq1V01kQaJ9Kz11zWrIdlrH0ZdnJ7GC6qA0ErROG+cXmJ0rJb8/SW1Zp2IA==" + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.76.8.tgz", + "integrity": "sha512-KccOqc10vrzS7ZhG2NSnL2dh3uVydarB7nOhjreQ7C4zyWuiW9XpLC4h47KtGQv3Rnv/NDLJYeDqaJ4/+140HQ==", + "dev": true }, "metro-runtime": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.76.7.tgz", - "integrity": "sha512-MuWHubQHymUWBpZLwuKZQgA/qbb35WnDAKPo83rk7JRLIFPvzXSvFaC18voPuzJBt1V98lKQIonh6MiC9gd8Ug==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.76.8.tgz", + "integrity": "sha512-XKahvB+iuYJSCr3QqCpROli4B4zASAYpkK+j3a0CJmokxCDNbgyI4Fp88uIL6rNaZfN0Mv35S0b99SdFXIfHjg==", "requires": { "@babel/runtime": "^7.0.0", "react-refresh": "^0.4.0" @@ -73214,16 +74383,16 @@ } }, "metro-source-map": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.76.7.tgz", - "integrity": "sha512-Prhx7PeRV1LuogT0Kn5VjCuFu9fVD68eefntdWabrksmNY6mXK8pRqzvNJOhTojh6nek+RxBzZeD6MIOOyXS6w==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.76.8.tgz", + "integrity": "sha512-Hh0ncPsHPVf6wXQSqJqB3K9Zbudht4aUtNpNXYXSxH+pteWqGAXnjtPsRAnCsCWl38wL0jYF0rJDdMajUI3BDw==", "requires": { "@babel/traverse": "^7.20.0", "@babel/types": "^7.20.0", "invariant": "^2.2.4", - "metro-symbolicate": "0.76.7", + "metro-symbolicate": "0.76.8", "nullthrows": "^1.1.1", - "ob1": "0.76.7", + "ob1": "0.76.8", "source-map": "^0.5.6", "vlq": "^1.0.0" }, @@ -73236,12 +74405,12 @@ } }, "metro-symbolicate": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.76.7.tgz", - "integrity": "sha512-p0zWEME5qLSL1bJb93iq+zt5fz3sfVn9xFYzca1TJIpY5MommEaS64Va87lp56O0sfEIvh4307Oaf/ZzRjuLiQ==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.76.8.tgz", + "integrity": "sha512-LrRL3uy2VkzrIXVlxoPtqb40J6Bf1mlPNmUQewipc3qfKKFgtPHBackqDy1YL0njDsWopCKcfGtFYLn0PTUn3w==", "requires": { "invariant": "^2.2.4", - "metro-source-map": "0.76.7", + "metro-source-map": "0.76.8", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "through2": "^2.0.1", @@ -73256,9 +74425,10 @@ } }, "metro-transform-plugins": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.76.7.tgz", - "integrity": "sha512-iSmnjVApbdivjuzb88Orb0JHvcEt5veVyFAzxiS5h0QB+zV79w6JCSqZlHCrbNOkOKBED//LqtKbFVakxllnNg==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.76.8.tgz", + "integrity": "sha512-PlkGTQNqS51Bx4vuufSQCdSn2R2rt7korzngo+b5GCkeX5pjinPjnO2kNhQ8l+5bO0iUD/WZ9nsM2PGGKIkWFA==", + "dev": true, "requires": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", @@ -73268,21 +74438,22 @@ } }, "metro-transform-worker": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.76.7.tgz", - "integrity": "sha512-cGvELqFMVk9XTC15CMVzrCzcO6sO1lURfcbgjuuPdzaWuD11eEyocvkTX0DPiRjsvgAmicz4XYxVzgYl3MykDw==", + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.76.8.tgz", + "integrity": "sha512-mE1fxVAnJKmwwJyDtThildxxos9+DGs9+vTrx2ktSFMEVTtXS/bIv2W6hux1pqivqAfyJpTeACXHk5u2DgGvIQ==", + "dev": true, "requires": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.0", "@babel/parser": "^7.20.0", "@babel/types": "^7.20.0", "babel-preset-fbjs": "^3.4.0", - "metro": "0.76.7", - "metro-babel-transformer": "0.76.7", - "metro-cache": "0.76.7", - "metro-cache-key": "0.76.7", - "metro-source-map": "0.76.7", - "metro-transform-plugins": "0.76.7", + "metro": "0.76.8", + "metro-babel-transformer": "0.76.8", + "metro-cache": "0.76.8", + "metro-cache-key": "0.76.8", + "metro-source-map": "0.76.8", + "metro-transform-plugins": "0.76.8", "nullthrows": "^1.1.1" } }, @@ -74324,9 +75495,9 @@ "dev": true }, "ob1": { - "version": "0.76.7", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.76.7.tgz", - "integrity": "sha512-BQdRtxxoUNfSoZxqeBGOyuT9nEYSn18xZHwGMb0mMVpn2NBcYbnyKY4BK2LIHRgw33CBGlUmE+KMaNvyTpLLtQ==" + "version": "0.76.8", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.76.8.tgz", + "integrity": "sha512-dlBkJJV5M/msj9KYA9upc+nUWVwuOFFTbu28X6kZeGwcuW+JxaHSBZ70SYQnk5M+j5JbNLR6yKHmgW4M5E7X5g==" }, "object-assign": { "version": "4.1.1" @@ -75787,20 +76958,20 @@ } }, "react-native": { - "version": "0.72.3", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.72.3.tgz", - "integrity": "sha512-QqISi+JVmCssNP2FlQ4MWhlc4O/I00MRE1/GClvyZ8h/6kdsyk/sOirkYdZqX3+DrJfI3q+OnyMnsyaXIQ/5tQ==", + "version": "0.72.4", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.72.4.tgz", + "integrity": "sha512-+vrObi0wZR+NeqL09KihAAdVlQ9IdplwznJWtYrjnQ4UbCW6rkzZJebRsugwUneSOKNFaHFEo1uKU89HsgtYBg==", "requires": { "@jest/create-cache-key-function": "^29.2.1", - "@react-native-community/cli": "11.3.5", - "@react-native-community/cli-platform-android": "11.3.5", - "@react-native-community/cli-platform-ios": "11.3.5", + "@react-native-community/cli": "11.3.6", + "@react-native-community/cli-platform-android": "11.3.6", + "@react-native-community/cli-platform-ios": "11.3.6", "@react-native/assets-registry": "^0.72.0", "@react-native/codegen": "^0.72.6", "@react-native/gradle-plugin": "^0.72.11", "@react-native/js-polyfills": "^0.72.1", "@react-native/normalize-colors": "^0.72.0", - "@react-native/virtualized-lists": "^0.72.6", + "@react-native/virtualized-lists": "^0.72.8", "abort-controller": "^3.0.0", "anser": "^1.4.9", "base64-js": "^1.1.2", @@ -75811,8 +76982,8 @@ "jest-environment-node": "^29.2.1", "jsc-android": "^250231.0.0", "memoize-one": "^5.0.0", - "metro-runtime": "0.76.7", - "metro-source-map": "0.76.7", + "metro-runtime": "0.76.8", + "metro-source-map": "0.76.8", "mkdirp": "^0.5.1", "nullthrows": "^1.1.1", "pretty-format": "^26.5.2", @@ -76038,9 +77209,9 @@ } }, "react-native-google-places-autocomplete": { - "version": "git+ssh://git@github.com/Expensify/react-native-google-places-autocomplete.git#fd212e1e93cad72e97efad03893bea6d074d3e07", - "integrity": "sha512-tuU9AIvDtbGe+zEIdFXEuoMQl3n+JHWvMpTpMDdih1evgMiiX4tZxnHmMPpvhrxs9DiPaT/1+92eqJtMI3s+fA==", - "from": "react-native-google-places-autocomplete@git+https://github.com/Expensify/react-native-google-places-autocomplete.git#fd212e1e93cad72e97efad03893bea6d074d3e07", + "version": "git+ssh://git@github.com/Expensify/react-native-google-places-autocomplete.git#cef3ac29d9501091453136e1219e24c4ec9f9d76", + "integrity": "sha512-2z3ED8jOXasPTzBqvPwpG10LQsBArTRsYszmoz+TfqbgZrSBmP3c8rhaC//lx6Pvfs2r+KYWqJUrLf4mbCrjZw==", + "from": "react-native-google-places-autocomplete@git+https://github.com/Expensify/react-native-google-places-autocomplete.git#cef3ac29d9501091453136e1219e24c4ec9f9d76", "requires": { "lodash.debounce": "^4.0.8", "prop-types": "^15.7.2", @@ -79056,7 +80227,9 @@ "dev": true }, "throat": { - "version": "5.0.0" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" }, "throttle-debounce": { "version": "3.0.1", @@ -79960,7 +81133,9 @@ "version": "1.2.8" }, "vlq": { - "version": "1.0.1" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==" }, "vm-browserify": { "version": "1.1.2" diff --git a/package.json b/package.json index 7b5500340bf7..6f0d4d70f768 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.71-8", + "version": "1.3.72-6", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -113,7 +113,7 @@ "react-dom": "18.1.0", "react-map-gl": "^7.1.3", "react-error-boundary": "^4.0.11", - "react-native": "0.72.3", + "react-native": "0.72.4", "react-native-blob-util": "^0.17.3", "react-native-collapsible": "^1.6.0", "react-native-config": "^1.4.5", @@ -123,7 +123,7 @@ "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.12.0", - "react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#fd212e1e93cad72e97efad03893bea6d074d3e07", + "react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#cef3ac29d9501091453136e1219e24c4ec9f9d76", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^5.1.0", @@ -132,7 +132,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "^1.0.84", + "react-native-onyx": "1.0.84", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", @@ -181,7 +181,7 @@ "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", "@react-native-community/eslint-config": "3.0.0", - "@react-native/metro-config": "^0.72.9", + "@react-native/metro-config": "^0.72.11", "@react-navigation/devtools": "^6.0.10", "@storybook/addon-a11y": "^6.5.9", "@storybook/addon-essentials": "^7.0.0", @@ -246,7 +246,7 @@ "jest-circus": "29.4.1", "jest-cli": "29.4.1", "jest-environment-jsdom": "^29.4.1", - "metro-react-native-babel-preset": "0.76.7", + "metro-react-native-babel-preset": "0.76.8", "mock-fs": "^4.13.0", "onchange": "^7.1.0", "portfinder": "^1.0.28", diff --git a/patches/react-native+0.72.3+001+initial.patch b/patches/react-native+0.72.4+001+initial.patch similarity index 100% rename from patches/react-native+0.72.3+001+initial.patch rename to patches/react-native+0.72.4+001+initial.patch diff --git a/patches/react-native+0.72.3+002+NumberOfLines.patch b/patches/react-native+0.72.4+002+NumberOfLines.patch similarity index 97% rename from patches/react-native+0.72.3+002+NumberOfLines.patch rename to patches/react-native+0.72.4+002+NumberOfLines.patch index 7d773297cb5f..75422f84708e 100644 --- a/patches/react-native+0.72.3+002+NumberOfLines.patch +++ b/patches/react-native+0.72.4+002+NumberOfLines.patch @@ -30,10 +30,10 @@ index 6f69329..d531bee 100644 maxLength: true, autoCapitalize: true, diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -index ff029fb..0835135 100644 +index 8badb2a..b19f197 100644 --- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts +++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -@@ -338,12 +338,6 @@ export interface TextInputAndroidProps { +@@ -347,12 +347,6 @@ export interface TextInputAndroidProps { */ inlineImagePadding?: number | undefined; @@ -46,7 +46,7 @@ index ff029fb..0835135 100644 /** * Sets the return key to the label. Use it instead of `returnKeyType`. * @platform android -@@ -654,11 +648,29 @@ export interface TextInputProps +@@ -663,11 +657,30 @@ export interface TextInputProps */ maxLength?: number | undefined; @@ -72,6 +72,7 @@ index ff029fb..0835135 100644 + * Use it with multiline set to true to be able to fill the lines. + */ + rows?: number | undefined; ++ + /** * Callback that is called when the text input is blurred @@ -147,10 +148,10 @@ index 7ed4579..b1d994e 100644 * If `true`, the text input obscures the text entered so that sensitive text * like passwords stay secure. The default value is `false`. Does not work with 'multiline={true}'. diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -index df89097..3b223ec 100644 +index 2127191..542fc06 100644 --- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js +++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -@@ -387,7 +387,6 @@ type AndroidProps = $ReadOnly<{| +@@ -390,7 +390,6 @@ type AndroidProps = $ReadOnly<{| /** * Sets the number of lines for a `TextInput`. Use it with multiline set to * `true` to be able to fill the lines. @@ -158,7 +159,7 @@ index df89097..3b223ec 100644 */ numberOfLines?: ?number, -@@ -400,10 +399,14 @@ type AndroidProps = $ReadOnly<{| +@@ -403,10 +402,14 @@ type AndroidProps = $ReadOnly<{| /** * Sets the number of rows for a `TextInput`. Use it with multiline set to * `true` to be able to fill the lines. @@ -174,7 +175,7 @@ index df89097..3b223ec 100644 /** * When `false`, it will prevent the soft keyboard from showing when the field is focused. * Defaults to `true`. -@@ -1066,6 +1069,9 @@ function InternalTextInput(props: Props): React.Node { +@@ -1069,6 +1072,9 @@ function InternalTextInput(props: Props): React.Node { accessibilityState, id, tabIndex, @@ -184,7 +185,7 @@ index df89097..3b223ec 100644 selection: propsSelection, ...otherProps } = props; -@@ -1422,6 +1428,8 @@ function InternalTextInput(props: Props): React.Node { +@@ -1427,6 +1433,8 @@ function InternalTextInput(props: Props): React.Node { focusable={tabIndex !== undefined ? !tabIndex : focusable} mostRecentEventCount={mostRecentEventCount} nativeID={id ?? props.nativeID} @@ -193,7 +194,7 @@ index df89097..3b223ec 100644 onBlur={_onBlur} onKeyPressSync={props.unstable_onKeyPressSync} onChange={_onChange} -@@ -1477,6 +1485,7 @@ function InternalTextInput(props: Props): React.Node { +@@ -1482,6 +1490,7 @@ function InternalTextInput(props: Props): React.Node { mostRecentEventCount={mostRecentEventCount} nativeID={id ?? props.nativeID} numberOfLines={props.rows ?? props.numberOfLines} @@ -549,7 +550,7 @@ index 190bc27..c2bcdc1 100644 : mEllipsizeLocation; setEllipsize(ellipsizeLocation); diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java -index 561a2d0..017be13 100644 +index 561a2d0..9409cfc 100644 --- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java @@ -18,6 +18,7 @@ import android.text.SpannableStringBuilder; @@ -568,7 +569,7 @@ index 561a2d0..017be13 100644 private static final LruCache sSpannableCache = new LruCache<>(spannableCacheSize); private static final ConcurrentHashMap sTagToSpannableCache = -@@ -385,6 +387,47 @@ public class TextLayoutManager { +@@ -385,6 +387,48 @@ public class TextLayoutManager { ? paragraphAttributes.getInt(MAXIMUM_NUMBER_OF_LINES_KEY) : UNSET; @@ -612,12 +613,13 @@ index 561a2d0..017be13 100644 + if (numberOfLines != UNSET && numberOfLines != 0) { + maximumNumberOfLines = numberOfLines; + } ++ + int calculatedLineCount = maximumNumberOfLines == UNSET || maximumNumberOfLines == 0 ? layout.getLineCount() diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java -index 0d118f0..f29f069 100644 +index 0d118f0..0ae44b7 100644 --- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java @@ -18,6 +18,7 @@ import android.text.SpannableStringBuilder; @@ -636,7 +638,7 @@ index 0d118f0..f29f069 100644 private static final boolean ENABLE_MEASURE_LOGGING = ReactBuildConfig.DEBUG && false; -@@ -399,6 +401,46 @@ public class TextLayoutManagerMapBuffer { +@@ -399,6 +401,47 @@ public class TextLayoutManagerMapBuffer { ? paragraphAttributes.getInt(PA_KEY_MAX_NUMBER_OF_LINES) : UNSET; @@ -679,15 +681,16 @@ index 0d118f0..f29f069 100644 + if (numberOfLines != UNSET && numberOfLines != 0) { + maximumNumberOfLines = numberOfLines; + } ++ + int calculatedLineCount = maximumNumberOfLines == UNSET || maximumNumberOfLines == 0 ? layout.getLineCount() diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java -index 1b5e0f4..67d0b73 100644 +index ced37be..ef2f321 100644 --- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java -@@ -483,7 +483,13 @@ public class ReactEditText extends AppCompatEditText +@@ -548,7 +548,13 @@ public class ReactEditText extends AppCompatEditText * href='https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/widget/TextView.java'>TextView.java} */ if (isMultiline()) { @@ -807,10 +810,10 @@ index f5f87c6..b7d1e90 100644 attributes.ellipsizeMode, attributes.textBreakStrategy, diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h -index 8687b89..26379f4 100644 +index 8687b89..eab75f4 100644 --- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h +++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h -@@ -835,12 +835,18 @@ inline ParagraphAttributes convertRawProp( +@@ -835,10 +835,16 @@ inline ParagraphAttributes convertRawProp( ParagraphAttributes const &defaultParagraphAttributes) { auto paragraphAttributes = ParagraphAttributes{}; @@ -819,19 +822,15 @@ index 8687b89..26379f4 100644 context, rawProps, "numberOfLines", -- sourceParagraphAttributes.maximumNumberOfLines, -- defaultParagraphAttributes.maximumNumberOfLines); + sourceParagraphAttributes.numberOfLines, + defaultParagraphAttributes.numberOfLines); + paragraphAttributes.maximumNumberOfLines = convertRawProp( -+ context, -+ rawProps, -+ "maximumNumberOfLines", -+ sourceParagraphAttributes.maximumNumberOfLines, -+ defaultParagraphAttributes.maximumNumberOfLines); ++ context, ++ rawProps, ++ "maximumNumberOfLines", + sourceParagraphAttributes.maximumNumberOfLines, + defaultParagraphAttributes.maximumNumberOfLines); paragraphAttributes.ellipsizeMode = convertRawProp( - context, - rawProps, @@ -913,6 +919,7 @@ inline std::string toString(AttributedString::Range const &range) { inline folly::dynamic toDynamic( const ParagraphAttributes ¶graphAttributes) { @@ -853,7 +852,7 @@ index 8687b89..26379f4 100644 PA_KEY_HYPHENATION_FREQUENCY, toString(paragraphAttributes.android_hyphenationFrequency)); + builder.putInt( -+ PA_KEY_NUMBER_OF_LINES, paragraphAttributes.numberOfLines); ++ PA_KEY_NUMBER_OF_LINES, paragraphAttributes.numberOfLines); return builder.build(); } @@ -914,15 +913,15 @@ index ba39ebb..ead28e3 100644 std::string textBreakStrategy{}; SharedColor underlineColorAndroid{}; diff --git a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm -index 368c334..2a98ad0 100644 +index 368c334..a1bb33e 100644 --- a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +++ b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm -@@ -244,26 +244,50 @@ - (void)getRectWithAttributedString:(AttributedString)attributedString +@@ -244,26 +244,51 @@ - (void)getRectWithAttributedString:(AttributedString)attributedString #pragma mark - Private -- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)attributedString -+- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)inputAttributedString +++- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)inputAttributedString paragraphAttributes:(ParagraphAttributes)paragraphAttributes size:(CGSize)size { @@ -950,7 +949,6 @@ index 368c334..2a98ad0 100644 - ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode) - : NSLineBreakByClipping; - textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines; -+ + [attributedString insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:attributesOfFirstCharacter] atIndex:0]; + } + @@ -959,7 +957,7 @@ index 368c334..2a98ad0 100644 NSLayoutManager *layoutManager = [NSLayoutManager new]; layoutManager.usesFontLeading = NO; [layoutManager addTextContainer:textContainer]; -- + - NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; + NSTextStorage *textStorage = [NSTextStorage new]; @@ -973,6 +971,7 @@ index 368c334..2a98ad0 100644 + textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines; + + [textStorage replaceCharactersInRange:(NSRange){0, textStorage.length} withAttributedString:attributedString]; ++ + if (paragraphAttributes.adjustsFontSizeToFit) { CGFloat minimumFontSize = !isnan(paragraphAttributes.minimumFontSize) ? paragraphAttributes.minimumFontSize : 4.0; diff --git a/patches/react-native+0.72.3+003+VerticalScrollBarPosition.patch b/patches/react-native+0.72.4+003+VerticalScrollBarPosition.patch similarity index 76% rename from patches/react-native+0.72.3+003+VerticalScrollBarPosition.patch rename to patches/react-native+0.72.4+003+VerticalScrollBarPosition.patch index 1cf8f6de54fb..e6ed0d4f79a3 100644 --- a/patches/react-native+0.72.3+003+VerticalScrollBarPosition.patch +++ b/patches/react-native+0.72.4+003+VerticalScrollBarPosition.patch @@ -1,12 +1,11 @@ diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java -index 46e0ccf..53293a4 100644 +index 33658e7..31c20c0 100644 --- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java -@@ -379,4 +379,15 @@ public class ReactScrollViewManager extends ViewGroupManager - public void setScrollEventThrottle(ReactScrollView view, int scrollEventThrottle) { +@@ -381,6 +381,17 @@ public class ReactScrollViewManager extends ViewGroupManager view.setScrollEventThrottle(scrollEventThrottle); } -+ + + @ReactProp(name = "verticalScrollbarPosition") + public void setVerticalScrollbarPosition(ReactScrollView view, String position) { + if ("right".equals(position)) { @@ -17,4 +16,7 @@ index 46e0ccf..53293a4 100644 + view.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_DEFAULT); + } + } - } ++ + @ReactProp(name = "isInvertedVirtualizedList") + public void setIsInvertedVirtualizedList(ReactScrollView view, boolean applyFix) { + // Usually when inverting the scroll view we are using scaleY: -1 on the list diff --git a/patches/react-native+0.72.3+004+ModalKeyboardFlashing.patch b/patches/react-native+0.72.4+004+ModalKeyboardFlashing.patch similarity index 100% rename from patches/react-native+0.72.3+004+ModalKeyboardFlashing.patch rename to patches/react-native+0.72.4+004+ModalKeyboardFlashing.patch diff --git a/src/CONST.ts b/src/CONST.ts index 4b2d704d1fa2..a4a5db491e0a 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -757,6 +757,9 @@ const CONST = { // 6 numeric digits VALIDATE_CODE_REGEX_STRING: /^\d{6}$/, + // 8 alphanumeric characters + RECOVERY_CODE_REGEX_STRING: /^[a-zA-Z0-9]{8}$/, + // The server has a WAF (Web Application Firewall) which will strip out HTML/XML tags using this regex pattern. // It's copied here so that the same regex pattern can be used in form validations to be consistent with the server. VALIDATE_FOR_HTML_TAG_REGEX: /<([^>\s]+)(?:[^>]*?)>/g, @@ -806,6 +809,8 @@ const CONST = { MAGIC_CODE_LENGTH: 6, MAGIC_CODE_EMPTY_CHAR: ' ', + RECOVERY_CODE_LENGTH: 8, + KEYBOARD_TYPE: { PHONE_PAD: 'phone-pad', NUMBER_PAD: 'number-pad', @@ -1352,6 +1357,7 @@ const CONST = { DATE: 'date', DESCRIPTION: 'description', MERCHANT: 'merchant', + RECEIPT: 'receipt', }, FOOTER: { EXPENSE_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/expense-management`, @@ -2650,6 +2656,12 @@ const CONST = { EVENTS: { SCROLLING: 'scrolling', }, + HORIZONTAL_SPACER: { + DEFAULT_BORDER_BOTTOM_WIDTH: 1, + DEFAULT_MARGIN_VERTICAL: 8, + HIDDEN_MARGIN_VERTICAL: 0, + HIDDEN_BORDER_BOTTOM_WIDTH: 0, + }, } as const; export default CONST; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index af060ea58901..05256f2b806c 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -170,6 +170,9 @@ const ONYXKEYS = { /** Is report data loading? */ IS_LOADING_REPORT_DATA: 'isLoadingReportData', + /** Is report data loading? */ + IS_LOADING_APP: 'isLoadingApp', + /** Is Keyboard shortcuts modal open? */ IS_SHORTCUTS_MODAL_OPEN: 'isShortcutsModalOpen', @@ -359,7 +362,6 @@ type OnyxValues = { [ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID]: string; [ONYXKEYS.PREFERRED_THEME]: ValueOf; [ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS]: boolean; - [ONYXKEYS.RECEIPT_MODAL]: OnyxTypes.ReceiptModal; [ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken; [ONYXKEYS.ONYX_UPDATES_FROM_SERVER]: OnyxTypes.OnyxUpdatesFromServer; [ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT]: number; diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index e2843ba7fae8..dbe7e46ff6aa 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -1,7 +1,6 @@ import _ from 'underscore'; -import React, {useEffect, useRef, useCallback, useMemo} from 'react'; +import React, {useEffect, useRef, useCallback} from 'react'; import {ActivityIndicator, View} from 'react-native'; -import {useIsFocused} from '@react-navigation/native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; @@ -39,9 +38,6 @@ const propTypes = { /** Fired when the user exits the Plaid flow */ onExitPlaid: PropTypes.func, - /** Fired when the screen is blurred */ - onBlurPlaid: PropTypes.func, - /** Fired when the user selects an account */ onSelect: PropTypes.func, @@ -65,7 +61,6 @@ const defaultProps = { selectedPlaidAccountID: '', plaidLinkToken: '', onExitPlaid: () => {}, - onBlurPlaid: () => {}, onSelect: () => {}, text: '', receivedRedirectURI: null, @@ -80,7 +75,6 @@ function AddPlaidBankAccount({ selectedPlaidAccountID, plaidLinkToken, onExitPlaid, - onBlurPlaid, onSelect, text, receivedRedirectURI, @@ -94,7 +88,6 @@ function AddPlaidBankAccount({ const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const isFocused = useIsFocused(); /** * @returns {String} @@ -109,11 +102,6 @@ function AddPlaidBankAccount({ } }; - /** - * @returns {Array} - */ - const plaidBankAccounts = useMemo(() => lodashGet(plaidData, 'bankAccounts') || [], [plaidData]); - /** * @returns {Boolean} * I'm using useCallback so the useEffect which uses this function doesn't run on every render. @@ -163,13 +151,6 @@ function AddPlaidBankAccount({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - if (isFocused || plaidBankAccounts.length) { - return; - } - onBlurPlaid(); - }, [isFocused, onBlurPlaid, plaidBankAccounts.length]); - useEffect(() => { // If we are coming back from offline and we haven't authenticated with Plaid yet, we need to re-run our call to kick off Plaid // previousNetworkState.current also makes sure that this doesn't run on the first render. @@ -179,6 +160,7 @@ function AddPlaidBankAccount({ previousNetworkState.current = isOffline; }, [allowDebit, bankAccountID, isAuthenticatedWithPlaid, isOffline]); + const plaidBankAccounts = lodashGet(plaidData, 'bankAccounts') || []; const token = getPlaidLinkToken(); const options = _.map(plaidBankAccounts, (account) => ({ value: account.plaidAccountID, diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index bbb0662132d2..946b5e2ddec9 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -1,4 +1,4 @@ -import React, {useState, useCallback} from 'react'; +import React, {useState, useCallback, useRef} from 'react'; import PropTypes from 'prop-types'; import {View, Animated, Keyboard} from 'react-native'; import Str from 'expensify-common/lib/str'; @@ -25,6 +25,10 @@ import HeaderGap from './HeaderGap'; import SafeAreaConsumer from './SafeAreaConsumer'; import addEncryptedAuthTokenToURL from '../libs/addEncryptedAuthTokenToURL'; import reportPropTypes from '../pages/reportPropTypes'; +import * as Expensicons from './Icon/Expensicons'; +import useWindowDimensions from '../hooks/useWindowDimensions'; +import Navigation from '../libs/Navigation/Navigation'; +import ROUTES from '../ROUTES'; import useNativeDriver from '../libs/useNativeDriver'; /** @@ -94,6 +98,7 @@ const defaultProps = { }; function AttachmentModal(props) { + const onModalHideCallbackRef = useRef(null); const [isModalOpen, setIsModalOpen] = useState(props.defaultOpen); const [shouldLoadAttachment, setShouldLoadAttachment] = useState(false); const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); @@ -106,6 +111,8 @@ function AttachmentModal(props) { const [isConfirmButtonDisabled, setIsConfirmButtonDisabled] = useState(false); const [confirmButtonFadeAnimation] = useState(new Animated.Value(1)); const [shouldShowDownloadButton, setShouldShowDownloadButton] = React.useState(true); + const {windowWidth} = useWindowDimensions(); + const [file, setFile] = useState( props.originalFileName ? { @@ -331,6 +338,10 @@ function AttachmentModal(props) { }} onModalHide={(e) => { props.onModalHide(e); + if (onModalHideCallbackRef.current) { + onModalHideCallbackRef.current(); + } + setShouldLoadAttachment(false); }} propagateSwipe @@ -339,12 +350,30 @@ function AttachmentModal(props) { downloadAttachment(source)} shouldShowCloseButton={!props.isSmallScreenWidth} shouldShowBackButton={props.isSmallScreenWidth} onBackButtonPress={closeModal} onCloseButtonPress={closeModal} + shouldShowThreeDotsButton={isAttachmentReceipt} + threeDotsAnchorPosition={styles.threeDotsPopoverOffsetAttachmentModal(windowWidth)} + threeDotsMenuItems={[ + { + icon: Expensicons.Camera, + text: props.translate('common.replace'), + onSelected: () => { + onModalHideCallbackRef.current = () => Navigation.navigate(ROUTES.getEditRequestRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)); + closeModal(); + }, + }, + { + icon: Expensicons.Download, + text: props.translate('common.download'), + onSelected: () => downloadAttachment(source), + }, + ]} + shouldOverlay /> {!_.isEmpty(props.report) ? ( diff --git a/src/components/BlockingViews/FullPageNotFoundView.js b/src/components/BlockingViews/FullPageNotFoundView.js index 54bdc015de37..a9c4bf63b65e 100644 --- a/src/components/BlockingViews/FullPageNotFoundView.js +++ b/src/components/BlockingViews/FullPageNotFoundView.js @@ -8,6 +8,7 @@ import Navigation from '../../libs/Navigation/Navigation'; import variables from '../../styles/variables'; import styles from '../../styles/styles'; import useLocalize from '../../hooks/useLocalize'; +import ROUTES from '../../ROUTES'; const propTypes = { /** Child elements */ @@ -44,7 +45,7 @@ const defaultProps = { titleKey: 'notFound.notHere', subtitleKey: 'notFound.pageNotFound', linkKey: 'notFound.goBackHome', - onBackButtonPress: Navigation.goBack, + onBackButtonPress: () => Navigation.goBack(ROUTES.HOME), shouldShowLink: true, shouldShowBackButton: true, onLinkPress: () => Navigation.dismissModal(), diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 966f700e25d4..09500fd9847a 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -189,7 +189,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken, useEffect(updateGradientVisibility, [scrollContainerHeight, scrollContentHeight]); const navigateBack = () => { - Navigation.goBack(isEditing ? ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID) : null); + Navigation.goBack(isEditing ? ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID) : ROUTES.HOME); }; const navigateToNextPage = () => { diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index bbf905cc1ac2..aab54612e206 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -22,7 +22,7 @@ import useKeyboardState from '../../hooks/useKeyboardState'; function HeaderWithBackButton({ iconFill = undefined, guidesCallTaskID = '', - onBackButtonPress = () => Navigation.goBack(), + onBackButtonPress = () => Navigation.goBack(ROUTES.HOME), onCloseButtonPress = () => Navigation.dismissModal(), onDownloadButtonPress = () => {}, onThreeDotsButtonPress = () => {}, @@ -47,6 +47,7 @@ function HeaderWithBackButton({ }, threeDotsMenuItems = [], children = null, + shouldOverlay = false, }) { const [isDownloadButtonActive, temporarilyDisableDownloadButton] = useThrottledButtonState(); const {translate} = useLocalize(); @@ -137,6 +138,7 @@ function HeaderWithBackButton({ menuItems={threeDotsMenuItems} onIconPress={onThreeDotsButtonPress} anchorPosition={threeDotsAnchorPosition} + shouldOverlay={shouldOverlay} /> )} {shouldShowCloseButton && ( diff --git a/src/components/MapView/MapView.web.tsx b/src/components/MapView/MapView.web.tsx index d1b26df8b00e..ce13cee10f54 100644 --- a/src/components/MapView/MapView.web.tsx +++ b/src/components/MapView/MapView.web.tsx @@ -44,6 +44,21 @@ const MapView = forwardRef( map.fitBounds([northEast, southWest], {padding: mapPadding}); }, [waypoints, mapRef, mapPadding]); + useEffect(() => { + if (!mapRef) { + return; + } + + const resizeObserver = new ResizeObserver(() => { + mapRef.resize(); + }); + resizeObserver.observe(mapRef.getContainer()); + + return () => { + resizeObserver?.disconnect(); + }; + }, [mapRef]); + useImperativeHandle( ref, () => ({ diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index da4e8d69682a..b00d4b1a62a5 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -424,6 +424,7 @@ function MoneyRequestConfirmationList(props) { sections={optionSelectorSections} value="" onSelectRow={canModifyParticipants ? selectParticipant : navigateToReportOrUserDetail} + onAddToSelection={selectParticipant} onConfirmSelection={confirm} selectedOptions={selectedOptions} canSelectMultipleOptions={canModifyParticipants} diff --git a/src/components/OptionsList/BaseOptionsList.js b/src/components/OptionsList/BaseOptionsList.js index 9b6875ccf8c2..5a40c28a86c9 100644 --- a/src/components/OptionsList/BaseOptionsList.js +++ b/src/components/OptionsList/BaseOptionsList.js @@ -174,7 +174,11 @@ function BaseOptionsList({ const renderItem = ({item, index, section}) => { const isItemDisabled = isDisabled || section.isDisabled || !!item.isDisabled; const isSelected = _.some(selectedOptions, (option) => { - if (option.accountID === item.accountID) { + if (option.accountID && option.accountID === item.accountID) { + return true; + } + + if (option.reportID && option.reportID === item.reportID) { return true; } diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index 492807274d1e..6b71b4a59055 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -149,7 +149,7 @@ function PopoverWithMeasuredContent(props) { but we can't measure its dimensions without first rendering it. */ {props.children} diff --git a/src/components/ReportActionItem/MoneyReportView.js b/src/components/ReportActionItem/MoneyReportView.js index 68eecf11d5bf..f593d947f96c 100644 --- a/src/components/ReportActionItem/MoneyReportView.js +++ b/src/components/ReportActionItem/MoneyReportView.js @@ -15,6 +15,7 @@ import variables from '../../styles/variables'; import * as CurrencyUtils from '../../libs/CurrencyUtils'; import EmptyStateBackgroundImage from '../../../assets/images/empty-state_background-fade.png'; import useLocalize from '../../hooks/useLocalize'; +import SpacerView from '../SpacerView'; const propTypes = { /** The report currently being looked at */ @@ -66,7 +67,10 @@ function MoneyReportView(props) { - {props.shouldShowHorizontalRule && } + ); } diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 712c7ded6ab0..d8b15653d8af 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -27,6 +27,7 @@ import Image from '../Image'; import ReportActionItemImage from './ReportActionItemImage'; import * as TransactionUtils from '../../libs/TransactionUtils'; import OfflineWithFeedback from '../OfflineWithFeedback'; +import SpacerView from '../SpacerView'; const propTypes = { /** The report currently being looked at */ @@ -91,7 +92,7 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans let hasErrors = false; if (hasReceipt) { receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); - hasErrors = TransactionUtils.hasMissingSmartscanFields(transaction); + hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction); } const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); @@ -169,7 +170,10 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans subtitleTextStyle={styles.textLabelError} /> - {shouldShowHorizontalRule && } + ); } diff --git a/src/components/ReportActionItem/TaskView.js b/src/components/ReportActionItem/TaskView.js index 8c6432a79878..f2a1758a050b 100644 --- a/src/components/ReportActionItem/TaskView.js +++ b/src/components/ReportActionItem/TaskView.js @@ -27,6 +27,7 @@ import getButtonState from '../../libs/getButtonState'; import PressableWithSecondaryInteraction from '../PressableWithSecondaryInteraction'; import * as Session from '../../libs/actions/Session'; import * as Expensicons from '../Icon/Expensicons'; +import SpacerView from '../SpacerView'; const propTypes = { /** The report currently being looked at */ @@ -158,7 +159,10 @@ function TaskView(props) { /> )} - {props.shouldShowHorizontalRule && } + ); } diff --git a/src/components/ReportActionsSkeletonView/SkeletonViewLines.js b/src/components/ReportActionsSkeletonView/SkeletonViewLines.js index ddaa46e0b731..e4432ceb2309 100644 --- a/src/components/ReportActionsSkeletonView/SkeletonViewLines.js +++ b/src/components/ReportActionsSkeletonView/SkeletonViewLines.js @@ -31,20 +31,20 @@ function SkeletonViewLines(props) { r="20" /> {props.numberOfRows > 1 && ( 2 && ( + { window.open(appleSignInWebRouteForDesktopFlow); diff --git a/src/components/SignInButtons/GoogleSignIn/index.desktop.js b/src/components/SignInButtons/GoogleSignIn/index.desktop.js index bdba2052d664..95a78f34614b 100644 --- a/src/components/SignInButtons/GoogleSignIn/index.desktop.js +++ b/src/components/SignInButtons/GoogleSignIn/index.desktop.js @@ -17,7 +17,7 @@ const googleSignInWebRouteForDesktopFlow = `${CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL */ function GoogleSignIn() { return ( - + { window.open(googleSignInWebRouteForDesktopFlow); diff --git a/src/components/SpacerView.js b/src/components/SpacerView.js new file mode 100644 index 000000000000..3b81bbfa0dc5 --- /dev/null +++ b/src/components/SpacerView.js @@ -0,0 +1,49 @@ +import React from 'react'; +import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import PropTypes from 'prop-types'; +import * as StyleUtils from '../styles/StyleUtils'; +import stylePropTypes from '../styles/stylePropTypes'; +import CONST from '../CONST'; + +const propTypes = { + /** + * Should we show the spacer + */ + shouldShow: PropTypes.bool.isRequired, + + /** + * Array of style objects + * @default [] + */ + style: stylePropTypes, +}; + +const defaultProps = { + style: [], +}; + +function SpacerView({shouldShow = true, style = []}) { + const marginVertical = useSharedValue(CONST.HORIZONTAL_SPACER.DEFAULT_MARGIN_VERTICAL); + const borderBottomWidth = useSharedValue(CONST.HORIZONTAL_SPACER.DEFAULT_BORDER_BOTTOM_WIDTH); + const animatedStyles = useAnimatedStyle(() => ({ + marginVertical: marginVertical.value, + borderBottomWidth: borderBottomWidth.value, + })); + + React.useEffect(() => { + const duration = CONST.ANIMATED_TRANSITION; + const values = { + marginVertical: shouldShow ? CONST.HORIZONTAL_SPACER.DEFAULT_MARGIN_VERTICAL : CONST.HORIZONTAL_SPACER.HIDDEN_MARGIN_VERTICAL, + borderBottomWidth: shouldShow ? CONST.HORIZONTAL_SPACER.DEFAULT_BORDER_BOTTOM_WIDTH : CONST.HORIZONTAL_SPACER.HIDDEN_BORDER_BOTTOM_WIDTH, + }; + marginVertical.value = withTiming(values.marginVertical, {duration}); + borderBottomWidth.value = withTiming(values.borderBottomWidth, {duration}); + }, [shouldShow, borderBottomWidth, marginVertical]); + + return ; +} + +SpacerView.displayName = 'SpacerView'; +SpacerView.propTypes = propTypes; +SpacerView.defaultProps = defaultProps; +export default SpacerView; diff --git a/src/components/ThreeDotsMenu/index.js b/src/components/ThreeDotsMenu/index.js index b5637a4f3879..f0cee6fdea2f 100644 --- a/src/components/ThreeDotsMenu/index.js +++ b/src/components/ThreeDotsMenu/index.js @@ -45,6 +45,9 @@ const propTypes = { horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)), vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), }), + + /** Whether the popover menu should overlay the current view */ + shouldOverlay: PropTypes.bool, }; const defaultProps = { @@ -57,9 +60,10 @@ const defaultProps = { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP }, + shouldOverlay: false, }; -function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment}) { +function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment, shouldOverlay}) { const [isPopupMenuVisible, setPopupMenuVisible] = useState(false); const buttonRef = useRef(null); const {translate} = useLocalize(); @@ -106,7 +110,7 @@ function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, me anchorAlignment={anchorAlignment} onItemSelected={hidePopoverMenu} menuItems={menuItems} - withoutOverlay + withoutOverlay={!shouldOverlay} anchorRef={buttonRef} /> diff --git a/src/languages/en.ts b/src/languages/en.ts index 416f5ff89fc2..210d82b28a7d 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -69,6 +69,7 @@ import type { SetTheRequestParams, UpdatedTheRequestParams, RemovedTheRequestParams, + RequestedAmountMessageParams, TagSelectionParams, TranslationBase, } from './types'; @@ -515,6 +516,7 @@ export default { settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => `Pay ${formattedAmount} with Expensify`, payElsewhere: 'Pay elsewhere', requestAmount: ({amount}: RequestAmountParams) => `request ${amount}`, + requestedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `requested ${formattedAmount}${comment ? ` for ${comment}` : ''}`, splitAmount: ({amount}: SplitAmountParams) => `split ${amount}`, amountEach: ({amount}: AmountEachParams) => `${amount} each`, payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer} owes ${amount}`, @@ -526,8 +528,8 @@ export default { waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} added a bank account. The ${amount} payment has been made.`, - paidElsewhereWithAmount: ({amount}: PaidElsewhereWithAmountParams) => `paid ${amount} elsewhere`, - paidWithExpensifyWithAmount: ({amount}: PaidWithExpensifyWithAmountParams) => `paid ${amount} with Expensify`, + paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} paid ${amount} elsewhere`, + paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer} paid ${amount} using Expensify`, noReimbursableExpenses: 'This report has an invalid amount', pendingConversionMessage: "Total will update when you're back online", changedTheRequest: 'changed the request', @@ -738,6 +740,15 @@ export default { copy: 'Copy', disable: 'Disable', }, + recoveryCodeForm: { + error: { + pleaseFillRecoveryCode: 'Please enter your recovery code', + incorrectRecoveryCode: 'Incorrect recovery code. Please try again.', + }, + useRecoveryCode: 'Use recovery code', + recoveryCode: 'Recovery code', + use2fa: 'Use two-factor authentication code', + }, twoFactorAuthForm: { error: { pleaseFillTwoFactorAuth: 'Please enter your two-factor authentication code', @@ -754,6 +765,7 @@ export default { sharedNoteMessage: 'Keep notes about this chat here. Expensify employees and other users on the team.expensify.com domain can view these notes.', notesUnavailable: 'No notes found for the user', composerLabel: 'Notes', + myNote: 'My note', }, addDebitCardPage: { addADebitCard: 'Add a debit card', @@ -890,6 +902,7 @@ export default { validateCodeForm: { magicCodeNotReceived: "Didn't receive a magic code?", enterAuthenticatorCode: 'Please enter your authenticator code', + enterRecoveryCode: 'Please enter your recovery code', requiredWhen2FAEnabled: 'Required when 2FA is enabled', requestNewCode: 'Request a new code in ', requestNewCodeAfterErrorOccurred: 'Request a new code', diff --git a/src/languages/es.ts b/src/languages/es.ts index 2606bfb7af1a..0048cfbb9e23 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -69,6 +69,7 @@ import type { SetTheRequestParams, UpdatedTheRequestParams, RemovedTheRequestParams, + RequestedAmountMessageParams, TagSelectionParams, EnglishTranslation, } from './types'; @@ -507,6 +508,7 @@ export default { settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => `Pagar ${formattedAmount} con Expensify`, payElsewhere: 'Pagar de otra forma', requestAmount: ({amount}: RequestAmountParams) => `solicitar ${amount}`, + requestedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `solicité ${formattedAmount}${comment ? ` para ${comment}` : ''}`, splitAmount: ({amount}: SplitAmountParams) => `dividir ${amount}`, amountEach: ({amount}: AmountEachParams) => `${amount} cada uno`, payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer} debe ${amount}`, @@ -518,8 +520,8 @@ export default { waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`, - paidElsewhereWithAmount: ({amount}: PaidElsewhereWithAmountParams) => `pagó ${amount} de otra forma`, - paidWithExpensifyWithAmount: ({amount}: PaidWithExpensifyWithAmountParams) => `pagó ${amount} con Expensify`, + paidElsewhereWithAmount: ({payer, amount}: PaidElsewhereWithAmountParams) => `${payer} pagó ${amount} de otra forma`, + paidWithExpensifyWithAmount: ({payer, amount}: PaidWithExpensifyWithAmountParams) => `${payer} pagó ${amount} con Expensify`, noReimbursableExpenses: 'El importe de este informe no es válido', pendingConversionMessage: 'El total se actualizará cuando estés online', changedTheRequest: 'cambió la solicitud', @@ -733,6 +735,15 @@ export default { copy: 'Copiar', disable: 'Deshabilitar', }, + recoveryCodeForm: { + error: { + pleaseFillRecoveryCode: 'Por favor, introduce tu código de recuperación', + incorrectRecoveryCode: 'Código de recuperación incorrecto. Por favor, inténtalo de nuevo', + }, + useRecoveryCode: 'Usar código de recuperación', + recoveryCode: 'Código de recuperación', + use2fa: 'Usar autenticación de dos factores', + }, twoFactorAuthForm: { error: { pleaseFillTwoFactorAuth: 'Por favor, introduce tu código de autenticación de dos factores', @@ -749,6 +760,7 @@ export default { sharedNoteMessage: 'Guarda notas sobre este chat aquí. Los empleados de Expensify y otros usuarios del dominio team.expensify.com pueden ver estas notas.', notesUnavailable: 'No se han encontrado notas para el usuario', composerLabel: 'Notas', + myNote: 'Mi notas', }, addDebitCardPage: { addADebitCard: 'Añadir una tarjeta de débito', @@ -886,6 +898,7 @@ export default { validateCodeForm: { magicCodeNotReceived: '¿No recibiste un código mágico?', enterAuthenticatorCode: 'Por favor, introduce el código de autenticador', + enterRecoveryCode: 'Por favor, introduce tu código de recuperación', requiredWhen2FAEnabled: 'Obligatorio cuando A2F está habilitado', requestNewCode: 'Pedir un código nuevo en ', requestNewCodeAfterErrorOccurred: 'Solicitar un nuevo código', diff --git a/src/languages/types.ts b/src/languages/types.ts index 86813841f177..9af00ceef8de 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -102,6 +102,8 @@ type SettleExpensifyCardParams = { type RequestAmountParams = {amount: number}; +type RequestedAmountMessageParams = {formattedAmount: string; comment: string}; + type SplitAmountParams = {amount: number}; type AmountEachParams = {amount: number}; @@ -122,9 +124,9 @@ type WaitingOnBankAccountParams = {submitterDisplayName: string}; type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string}; -type PaidElsewhereWithAmountParams = {amount: string}; +type PaidElsewhereWithAmountParams = {payer: string; amount: string}; -type PaidWithExpensifyWithAmountParams = {amount: string}; +type PaidWithExpensifyWithAmountParams = {payer: string; amount: string}; type ThreadRequestReportNameParams = {formattedAmount: string; comment: string}; @@ -257,6 +259,7 @@ export type { RequestCountParams, SettleExpensifyCardParams, RequestAmountParams, + RequestedAmountMessageParams, SplitAmountParams, AmountEachParams, PayerOwesAmountParams, diff --git a/src/libs/EmojiTrie.js b/src/libs/EmojiTrie.ts similarity index 53% rename from src/libs/EmojiTrie.js rename to src/libs/EmojiTrie.ts index 00e5fc1388e1..d0a53acf29c9 100644 --- a/src/libs/EmojiTrie.js +++ b/src/libs/EmojiTrie.ts @@ -1,29 +1,61 @@ -import _ from 'underscore'; +import React from 'react'; +import {SvgProps} from 'react-native-svg'; import emojis, {localeEmojis} from '../../assets/emojis'; import Trie from './Trie'; import Timing from './actions/Timing'; import CONST from '../CONST'; +type Emoji = { + code: string; + header?: boolean; + icon?: React.FC; + name?: string; + types?: string[]; +}; + +type LocalizedEmoji = { + name?: string; + keywords: string[]; +}; + +type LocalizedEmojis = Record; + +type Suggestion = { + code: string; + types?: string[]; + name?: string; +}; + +type EmojiMetaData = { + suggestions?: Suggestion[]; +}; + Timing.start(CONST.TIMING.TRIE_INITIALIZATION); -const supportedLanguages = [CONST.LOCALES.DEFAULT, CONST.LOCALES.ES]; +const supportedLanguages = [CONST.LOCALES.DEFAULT, CONST.LOCALES.ES] as const; + +type SupportedLanguage = (typeof supportedLanguages)[number]; + +type EmojiTrie = { + [key in SupportedLanguage]?: Trie; +}; /** * - * @param {Trie} trie The Trie object. - * @param {Array} keywords An array containing the keywords. - * @param {Object} item An object containing the properties of the emoji. - * @param {String} name The localized name of the emoji. - * @param {Boolean} shouldPrependKeyword Prepend the keyword (instead of append) to the suggestions + * @param trie The Trie object. + * @param keywords An array containing the keywords. + * @param item An object containing the properties of the emoji. + * @param name The localized name of the emoji. + * @param shouldPrependKeyword Prepend the keyword (instead of append) to the suggestions */ -function addKeywordsToTrie(trie, keywords, item, name, shouldPrependKeyword = false) { - _.forEach(keywords, (keyword) => { +function addKeywordsToTrie(trie: Trie, keywords: string[], item: Emoji, name: string, shouldPrependKeyword = false) { + keywords.forEach((keyword) => { const keywordNode = trie.search(keyword); if (!keywordNode) { trie.add(keyword, {suggestions: [{code: item.code, types: item.types, name}]}); } else { const suggestion = {code: item.code, types: item.types, name}; - const suggestions = shouldPrependKeyword ? [suggestion, ...keywordNode.metaData.suggestions] : [...keywordNode.metaData.suggestions, suggestion]; + const suggestions = shouldPrependKeyword ? [suggestion, ...(keywordNode.metaData.suggestions ?? [])] : [...(keywordNode.metaData.suggestions ?? []), suggestion]; trie.update(keyword, { ...keywordNode.metaData, suggestions, @@ -35,26 +67,27 @@ function addKeywordsToTrie(trie, keywords, item, name, shouldPrependKeyword = fa /** * Allows searching based on parts of the name. This turns 'white_large_square' into ['white_large_square', 'large_square', 'square']. * - * @param {String} name The emoji name - * @returns {Array} An array containing the name parts + * @param name The emoji name + * @returns An array containing the name parts */ -function getNameParts(name) { +function getNameParts(name: string): string[] { const nameSplit = name.split('_'); - return _.map(nameSplit, (_namePart, index) => nameSplit.slice(index).join('_')); + return nameSplit.map((namePart, index) => nameSplit.slice(index).join('_')); } -function createTrie(lang = CONST.LOCALES.DEFAULT) { +function createTrie(lang: SupportedLanguage = CONST.LOCALES.DEFAULT): Trie { const trie = new Trie(); - const langEmojis = localeEmojis[lang]; + const langEmojis: LocalizedEmojis = localeEmojis[lang]; + const defaultLangEmojis: LocalizedEmojis = localeEmojis[CONST.LOCALES.DEFAULT]; const isDefaultLocale = lang === CONST.LOCALES.DEFAULT; - _.forEach(emojis, (item) => { - if (item.header) { + emojis.forEach((item: Emoji) => { + if (!item.name) { return; } const englishName = item.name; - const localeName = _.get(langEmojis, [item.code, 'name'], englishName); + const localeName = langEmojis?.[item.code]?.name ?? englishName; const node = trie.search(localeName); if (!node) { @@ -67,7 +100,7 @@ function createTrie(lang = CONST.LOCALES.DEFAULT) { addKeywordsToTrie(trie, nameParts, item, localeName); // Add keywords for both the locale language and English to enable users to search using either language. - const keywords = _.get(langEmojis, [item.code, 'keywords'], []).concat(isDefaultLocale ? [] : _.get(localeEmojis, [CONST.LOCALES.DEFAULT, item.code, 'keywords'], [])); + const keywords = (langEmojis?.[item.code]?.keywords ?? []).concat(isDefaultLocale ? [] : defaultLangEmojis?.[item.code]?.keywords ?? []); addKeywordsToTrie(trie, keywords, item, localeName); /** @@ -83,7 +116,7 @@ function createTrie(lang = CONST.LOCALES.DEFAULT) { return trie; } -const emojiTrie = _.reduce(supportedLanguages, (prev, cur) => ({...prev, [cur]: createTrie(cur)}), {}); +const emojiTrie: EmojiTrie = supportedLanguages.reduce((prev, cur) => ({...prev, [cur]: createTrie(cur)}), {}); Timing.end(CONST.TIMING.TRIE_INITIALIZATION); diff --git a/src/libs/FormHelper.js b/src/libs/FormHelper.js deleted file mode 100644 index feab0f44acea..000000000000 --- a/src/libs/FormHelper.js +++ /dev/null @@ -1,50 +0,0 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; -import lodashUnset from 'lodash/unset'; -import lodashCloneDeep from 'lodash/cloneDeep'; - -class FormHelper { - constructor({errorPath, setErrors}) { - this.errorPath = errorPath; - this.setErrors = setErrors; - this.getErrors = this.getErrors.bind(this); - this.clearError = this.clearError.bind(this); - this.clearErrors = this.clearErrors.bind(this); - } - - /** - * @param {Object} props - * @returns {Object} - */ - getErrors(props) { - return lodashGet(props, this.errorPath, {}); - } - - /** - * @param {Object} props - * @param {String[]} paths - */ - clearErrors(props, paths) { - const errors = this.getErrors(props); - const pathsWithErrors = _.filter(paths, (path) => lodashGet(errors, path, false)); - if (_.size(pathsWithErrors) === 0) { - // No error found for this path - return; - } - - // Clear the existing errors - const newErrors = lodashCloneDeep(errors); - _.forEach(pathsWithErrors, (path) => lodashUnset(newErrors, path)); - this.setErrors(newErrors); - } - - /** - * @param {Object} props - * @param {String} path - */ - clearError(props, path) { - this.clearErrors(props, [path]); - } -} - -export default FormHelper; diff --git a/src/libs/IOUUtils.js b/src/libs/IOUUtils.ts similarity index 51% rename from src/libs/IOUUtils.js rename to src/libs/IOUUtils.ts index 2042c6beda05..6f6024506985 100644 --- a/src/libs/IOUUtils.js +++ b/src/libs/IOUUtils.ts @@ -1,18 +1,17 @@ -import _ from 'underscore'; import CONST from '../CONST'; import * as TransactionUtils from './TransactionUtils'; import * as CurrencyUtils from './CurrencyUtils'; +import {Report, Transaction} from '../types/onyx'; /** * Calculates the amount per user given a list of participants * - * @param {Number} numberOfParticipants - Number of participants in the chat. It should not include the current user. - * @param {Number} total - IOU total amount in backend format (cents, no matter the currency) - * @param {String} currency - This is used to know how many decimal places are valid to use when splitting the total - * @param {Boolean} isDefaultUser - Whether we are calculating the amount for the current user - * @returns {Number} + * @param numberOfParticipants - Number of participants in the chat. It should not include the current user. + * @param total - IOU total amount in backend format (cents, no matter the currency) + * @param currency - This is used to know how many decimal places are valid to use when splitting the total + * @param isDefaultUser - Whether we are calculating the amount for the current user */ -function calculateAmount(numberOfParticipants, total, currency, isDefaultUser = false) { +function calculateAmount(numberOfParticipants: number, total: number, currency: string, isDefaultUser = false): number { // Since the backend can maximum store 2 decimal places, any currency with more than 2 decimals // has to be capped to 2 decimal places const currencyUnit = Math.min(100, CurrencyUtils.getCurrencyUnit(currency)); @@ -34,35 +33,32 @@ function calculateAmount(numberOfParticipants, total, currency, isDefaultUser = * For example: if user1 owes user2 $10, then we have: {ownerAccountID: user2, managerID: user1, total: $10 (a positive amount, owed to user2)} * If user1 requests $17 from user2, then we have: {ownerAccountID: user1, managerID: user2, total: $7 (still a positive amount, but now owed to user1)} * - * @param {Object} iouReport - * @param {Number} actorAccountID - * @param {Number} amount - * @param {String} currency - * @param {String} isDeleting - whether the user is deleting the request - * @returns {Object} + * @param isDeleting - whether the user is deleting the request */ -function updateIOUOwnerAndTotal(iouReport, actorAccountID, amount, currency, isDeleting = false) { +function updateIOUOwnerAndTotal(iouReport: Report, actorAccountID: number, amount: number, currency: string, isDeleting = false): Report { if (currency !== iouReport.currency) { return iouReport; } // Make a copy so we don't mutate the original object - const iouReportUpdate = {...iouReport}; + const iouReportUpdate: Report = {...iouReport}; - if (actorAccountID === iouReport.ownerAccountID) { - iouReportUpdate.total += isDeleting ? -amount : amount; - } else { - iouReportUpdate.total += isDeleting ? amount : -amount; - } + if (iouReportUpdate.total) { + if (actorAccountID === iouReport.ownerAccountID) { + iouReportUpdate.total += isDeleting ? -amount : amount; + } else { + iouReportUpdate.total += isDeleting ? amount : -amount; + } - if (iouReportUpdate.total < 0) { - // The total sign has changed and hence we need to flip the manager and owner of the report. - iouReportUpdate.ownerAccountID = iouReport.managerID; - iouReportUpdate.managerID = iouReport.ownerAccountID; - iouReportUpdate.total = -iouReportUpdate.total; - } + if (iouReportUpdate.total < 0) { + // The total sign has changed and hence we need to flip the manager and owner of the report. + iouReportUpdate.ownerAccountID = iouReport.managerID; + iouReportUpdate.managerID = iouReport.ownerAccountID; + iouReportUpdate.total = -iouReportUpdate.total; + } - iouReportUpdate.hasOutstandingIOU = iouReportUpdate.total !== 0; + iouReportUpdate.hasOutstandingIOU = iouReportUpdate.total !== 0; + } return iouReportUpdate; } @@ -70,23 +66,19 @@ function updateIOUOwnerAndTotal(iouReport, actorAccountID, amount, currency, isD /** * Returns whether or not an IOU report contains money requests in a different currency * that are either created or cancelled offline, and thus haven't been converted to the report's currency yet - * - * @param {Object} iouReport - * @returns {Boolean} */ -function isIOUReportPendingCurrencyConversion(iouReport) { - const reportTransactions = TransactionUtils.getAllReportTransactions(iouReport.reportID); - const pendingRequestsInDifferentCurrency = _.filter(reportTransactions, (transaction) => transaction.pendingAction && TransactionUtils.getCurrency(transaction) !== iouReport.currency); +function isIOUReportPendingCurrencyConversion(iouReport: Report): boolean { + const reportTransactions: Transaction[] = TransactionUtils.getAllReportTransactions(iouReport.reportID); + const pendingRequestsInDifferentCurrency = reportTransactions.filter((transaction) => transaction.pendingAction && TransactionUtils.getCurrency(transaction) !== iouReport.currency); return pendingRequestsInDifferentCurrency.length > 0; } /** * Checks if the iou type is one of request, send, or split. - * @param {String} iouType - * @returns {Boolean} */ -function isValidMoneyRequestType(iouType) { - return [CONST.IOU.MONEY_REQUEST_TYPE.REQUEST, CONST.IOU.MONEY_REQUEST_TYPE.SPLIT].includes(iouType); +function isValidMoneyRequestType(iouType: string): boolean { + const moneyRequestType: string[] = [CONST.IOU.MONEY_REQUEST_TYPE.REQUEST, CONST.IOU.MONEY_REQUEST_TYPE.SPLIT]; + return moneyRequestType.includes(iouType); } export {calculateAmount, updateIOUOwnerAndTotal, isIOUReportPendingCurrencyConversion, isValidMoneyRequestType}; diff --git a/src/libs/MoneyRequestUtils.js b/src/libs/MoneyRequestUtils.ts similarity index 53% rename from src/libs/MoneyRequestUtils.js rename to src/libs/MoneyRequestUtils.ts index e60eae0cdfe5..b8a6a3da303f 100644 --- a/src/libs/MoneyRequestUtils.js +++ b/src/libs/MoneyRequestUtils.ts @@ -1,47 +1,36 @@ -import lodashGet from 'lodash/get'; -import _ from 'underscore'; +import {ValueOf} from 'type-fest'; import CONST from '../CONST'; /** * Strip comma from the amount - * - * @param {String} amount - * @returns {String} */ -function stripCommaFromAmount(amount) { +function stripCommaFromAmount(amount: string): string { return amount.replace(/,/g, ''); } /** * Strip spaces from the amount - * - * @param {String} amount - * @returns {String} */ -function stripSpacesFromAmount(amount) { +function stripSpacesFromAmount(amount: string): string { return amount.replace(/\s+/g, ''); } /** * Adds a leading zero to the amount if user entered just the decimal separator * - * @param {String} amount - Changed amount from user input - * @returns {String} + * @param amount - Changed amount from user input */ -function addLeadingZero(amount) { +function addLeadingZero(amount: string): string { return amount === '.' ? '0.' : amount; } /** * Calculate the length of the amount with leading zeroes - * - * @param {String} amount - * @returns {Number} */ -function calculateAmountLength(amount) { +function calculateAmountLength(amount: string): number { const leadingZeroes = amount.match(/^0+/); - const leadingZeroesLength = lodashGet(leadingZeroes, '[0].length', 0); - const absAmount = parseFloat((stripCommaFromAmount(amount) * 100).toFixed(2)).toString(); + const leadingZeroesLength = leadingZeroes?.[0]?.length ?? 0; + const absAmount = parseFloat((Number(stripCommaFromAmount(amount)) * 100).toFixed(2)).toString(); if (/\D/.test(absAmount)) { return CONST.IOU.AMOUNT_MAX_LENGTH + 1; @@ -52,11 +41,8 @@ function calculateAmountLength(amount) { /** * Check if amount is a decimal up to 3 digits - * - * @param {String} amount - * @returns {Boolean} */ -function validateAmount(amount) { +function validateAmount(amount: string): boolean { const decimalNumberRegex = new RegExp(/^\d+(,\d+)*(\.\d{0,2})?$/, 'i'); return amount === '' || (decimalNumberRegex.test(amount) && calculateAmountLength(amount) <= CONST.IOU.AMOUNT_MAX_LENGTH); } @@ -64,13 +50,10 @@ function validateAmount(amount) { /** * Replaces each character by calling `convertFn`. If `convertFn` throws an error, then * the original character will be preserved. - * - * @param {String} text - * @param {Function} convertFn - `fromLocaleDigit` or `toLocaleDigit` - * @returns {String} */ -function replaceAllDigits(text, convertFn) { - return _.chain([...text]) +function replaceAllDigits(text: string, convertFn: (char: string) => string): string { + return text + .split('') .map((char) => { try { return convertFn(char); @@ -78,19 +61,21 @@ function replaceAllDigits(text, convertFn) { return char; } }) - .join('') - .value(); + .join(''); } /** * Check if distance request or not - * - * @param {String} iouType - `send` | `split` | `request` - * @param {String} selectedTab - `manual` | `scan` | `distance` - * @returns {Boolean} */ -function isDistanceRequest(iouType, selectedTab) { +function isDistanceRequest(iouType: ValueOf, selectedTab: ValueOf): boolean { return iouType === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; } -export {stripCommaFromAmount, stripSpacesFromAmount, addLeadingZero, validateAmount, replaceAllDigits, isDistanceRequest}; +/** + * Check if scan request or not + */ +function isScanRequest(selectedTab: ValueOf): boolean { + return selectedTab === CONST.TAB.SCAN; +} + +export {stripCommaFromAmount, stripSpacesFromAmount, addLeadingZero, validateAmount, replaceAllDigits, isDistanceRequest, isScanRequest}; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index e0197805f09c..16d0e2225007 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -38,6 +38,8 @@ import DemoSetupPage from '../../../pages/DemoSetupPage'; let timezone; let currentAccountID; +let isLoadingApp; + Onyx.connect({ key: ONYXKEYS.SESSION, callback: (val) => { @@ -75,6 +77,13 @@ Onyx.connect({ }, }); +Onyx.connect({ + key: ONYXKEYS.IS_LOADING_APP, + callback: (val) => { + isLoadingApp = val; + }, +}); + const RootStack = createCustomStackNavigator(); // We want to delay the re-rendering for components(e.g. ReportActionCompose) @@ -126,7 +135,13 @@ class AuthScreens extends React.Component { componentDidMount() { NetworkConnection.listenForReconnect(); - NetworkConnection.onReconnect(() => App.reconnectApp(this.props.lastUpdateIDAppliedToClient)); + NetworkConnection.onReconnect(() => { + if (isLoadingApp) { + App.openApp(); + } else { + App.reconnectApp(this.props.lastUpdateIDAppliedToClient); + } + }); PusherConnectionManager.init(); Pusher.init({ appKey: CONFIG.PUSHER.APP_KEY, @@ -182,10 +197,10 @@ class AuthScreens extends React.Component { chatShortcutConfig.shortcutKey, () => { Modal.close(() => { - if (Navigation.isActiveRoute(ROUTES.NEW_CHAT)) { + if (Navigation.isActiveRoute(ROUTES.NEW)) { return; } - Navigation.navigate(ROUTES.NEW_CHAT); + Navigation.navigate(ROUTES.NEW); }); }, chatShortcutConfig.descriptionKey, diff --git a/src/libs/Navigation/Navigation.js b/src/libs/Navigation/Navigation.js index b574b4ffa205..1264ec777b28 100644 --- a/src/libs/Navigation/Navigation.js +++ b/src/libs/Navigation/Navigation.js @@ -97,7 +97,7 @@ function navigate(route = ROUTES.HOME, type) { * @param {Bool} shouldEnforceFallback - Enforces navigation to fallback route * @param {Bool} shouldPopToTop - Should we navigate to LHN on back press */ -function goBack(fallbackRoute = ROUTES.HOME, shouldEnforceFallback = false, shouldPopToTop = false) { +function goBack(fallbackRoute, shouldEnforceFallback = false, shouldPopToTop = false) { if (!canNavigate('goBack')) { return; } diff --git a/src/libs/Navigation/NavigationRoot.js b/src/libs/Navigation/NavigationRoot.js index 9e3cad6144dd..d8cb96e2c6b3 100644 --- a/src/libs/Navigation/NavigationRoot.js +++ b/src/libs/Navigation/NavigationRoot.js @@ -11,8 +11,6 @@ import Log from '../Log'; import StatusBar from '../StatusBar'; import useCurrentReportID from '../../hooks/useCurrentReportID'; import useWindowDimensions from '../../hooks/useWindowDimensions'; -import * as ReportActionContextMenu from '../../pages/home/report/ContextMenu/ReportActionContextMenu'; -import * as EmojiPickerAction from '../actions/EmojiPickerAction'; // https://reactnavigation.org/docs/themes const navigationTheme = { @@ -123,10 +121,6 @@ function NavigationRoot(props) { if (!state) { return; } - ReportActionContextMenu.hideContextMenu(); - ReportActionContextMenu.hideDeleteModal(); - EmojiPickerAction.hideEmojiPicker(true); - updateCurrentReportID(state); parseAndLogRoute(state); animateStatusBarBackgroundColor(); diff --git a/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.js b/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.js new file mode 100644 index 000000000000..0afc8fe10490 --- /dev/null +++ b/src/libs/Notification/PushNotification/ForegroundNotifications/index.android.js @@ -0,0 +1,15 @@ +import Airship from '@ua/react-native-airship'; +import shouldShowPushNotification from '../shouldShowPushNotification'; + +function configureForegroundNotifications() { + Airship.push.android.setForegroundDisplayPredicate((pushPayload) => Promise.resolve(shouldShowPushNotification(pushPayload))); +} + +function disableForegroundNotifications() { + Airship.push.android.setForegroundDisplayPredicate(() => Promise.resolve(false)); +} + +export default { + configureForegroundNotifications, + disableForegroundNotifications, +}; diff --git a/src/libs/Notification/PushNotification/configureForegroundNotifications/index.ios.js b/src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.js similarity index 78% rename from src/libs/Notification/PushNotification/configureForegroundNotifications/index.ios.js rename to src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.js index 88d94b4ee805..17ad1baaebe3 100644 --- a/src/libs/Notification/PushNotification/configureForegroundNotifications/index.ios.js +++ b/src/libs/Notification/PushNotification/ForegroundNotifications/index.ios.js @@ -1,7 +1,7 @@ import Airship, {iOS} from '@ua/react-native-airship'; import shouldShowPushNotification from '../shouldShowPushNotification'; -export default function configureForegroundNotifications() { +function configureForegroundNotifications() { // Set our default iOS foreground presentation to be loud with a banner // More info here https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649518-usernotificationcenter Airship.push.iOS.setForegroundPresentationOptions([ @@ -15,3 +15,12 @@ export default function configureForegroundNotifications() { // Returning null keeps the default presentation. Returning [] uses no presentation (hides the notification). Airship.push.iOS.setForegroundPresentationOptionsCallback((pushPayload) => Promise.resolve(shouldShowPushNotification(pushPayload) ? null : [])); } + +function disableForegroundNotifications() { + Airship.push.iOS.setForegroundPresentationOptionsCallback(() => Promise.resolve([])); +} + +export default { + configureForegroundNotifications, + disableForegroundNotifications, +}; diff --git a/src/libs/Notification/PushNotification/configureForegroundNotifications/index.js b/src/libs/Notification/PushNotification/ForegroundNotifications/index.js similarity index 52% rename from src/libs/Notification/PushNotification/configureForegroundNotifications/index.js rename to src/libs/Notification/PushNotification/ForegroundNotifications/index.js index c6cb13a0b3b9..acb116f7bc43 100644 --- a/src/libs/Notification/PushNotification/configureForegroundNotifications/index.js +++ b/src/libs/Notification/PushNotification/ForegroundNotifications/index.js @@ -1,4 +1,7 @@ /** * Configures notification handling while in the foreground on iOS and Android. This is a no-op on other platforms. */ -export default function () {} +export default { + configureForegroundNotifications: () => {}, + disableForegroundNotifications: () => {}, +}; diff --git a/src/libs/Notification/PushNotification/configureForegroundNotifications/index.android.js b/src/libs/Notification/PushNotification/configureForegroundNotifications/index.android.js deleted file mode 100644 index 393072df3d12..000000000000 --- a/src/libs/Notification/PushNotification/configureForegroundNotifications/index.android.js +++ /dev/null @@ -1,6 +0,0 @@ -import Airship from '@ua/react-native-airship'; -import shouldShowPushNotification from '../shouldShowPushNotification'; - -export default function configureForegroundNotifications() { - Airship.push.android.setForegroundDisplayPredicate((pushPayload) => Promise.resolve(shouldShowPushNotification(pushPayload))); -} diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js index 299af69873f9..7192ee66a791 100644 --- a/src/libs/Notification/PushNotification/index.native.js +++ b/src/libs/Notification/PushNotification/index.native.js @@ -6,7 +6,7 @@ import Log from '../../Log'; import NotificationType from './NotificationType'; import * as PushNotification from '../../actions/PushNotification'; import ONYXKEYS from '../../../ONYXKEYS'; -import configureForegroundNotifications from './configureForegroundNotifications'; +import ForegroundNotifications from './ForegroundNotifications'; let isUserOptedInToPushNotifications = false; Onyx.connect({ @@ -96,7 +96,7 @@ function init() { // Keep track of which users have enabled push notifications via an NVP. Airship.addListener(EventType.NotificationOptInStatus, refreshNotificationOptInStatus); - configureForegroundNotifications(); + ForegroundNotifications.configureForegroundNotifications(); } /** @@ -136,6 +136,7 @@ function deregister() { Airship.contact.reset(); Airship.removeAllListeners(EventType.PushReceived); Airship.removeAllListeners(EventType.NotificationResponse); + ForegroundNotifications.disableForegroundNotifications(); } /** diff --git a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js index a36fef610a39..8e16bb72f656 100644 --- a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js +++ b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js @@ -27,7 +27,7 @@ export default function subscribeToReportCommentPushNotifications() { try { // If a chat is visible other than the one we are trying to navigate to, then we need to navigate back if (Navigation.getActiveRoute().slice(1, 2) === ROUTES.REPORT && !Navigation.isActiveRoute(`r/${reportID}`)) { - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); } Log.info('[PushNotification] onSelected() - Navigation is ready. Navigating...', false, {reportID, reportActionID}); diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 8705d9f78004..8587ae1933db 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -92,31 +92,29 @@ Onyx.connect({ }); /** - * Get the options for a policy expense report. + * Get the option for a policy expense report. * @param {Object} report - * @returns {Array} + * @returns {Object} */ -function getPolicyExpenseReportOptions(report) { +function getPolicyExpenseReportOption(report) { const expenseReport = policyExpenseReports[`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]; const policyExpenseChatAvatarSource = ReportUtils.getWorkspaceAvatar(expenseReport); const reportName = ReportUtils.getReportName(expenseReport); - return [ - { - ...expenseReport, - keyForList: expenseReport.policyID, - text: reportName, - alternateText: Localize.translateLocal('workspace.common.workspace'), - icons: [ - { - source: policyExpenseChatAvatarSource, - name: reportName, - type: CONST.ICON_TYPE_WORKSPACE, - }, - ], - selected: report.selected, - isPolicyExpenseChat: true, - }, - ]; + return { + ...expenseReport, + keyForList: expenseReport.policyID, + text: reportName, + alternateText: Localize.translateLocal('workspace.common.workspace'), + icons: [ + { + source: policyExpenseChatAvatarSource, + name: reportName, + type: CONST.ICON_TYPE_WORKSPACE, + }, + ], + selected: report.selected, + isPolicyExpenseChat: true, + }; } /** @@ -201,37 +199,34 @@ function isPersonalDetailsReady(personalDetails) { } /** - * Get the participant options for a report. - * @param {Array} participants + * Get the participant option for a report. + * @param {Object} participant * @param {Array} personalDetails - * @returns {Array} + * @returns {Object} */ -function getParticipantsOptions(participants, personalDetails) { - const details = getPersonalDetailsForAccountIDs(_.pluck(participants, 'accountID'), personalDetails); - return _.map(participants, (participant) => { - const detail = details[participant.accountID]; - const login = detail.login || participant.login; - const displayName = detail.displayName || LocalePhoneNumber.formatPhoneNumber(login); - return { - keyForList: String(detail.accountID), - login, - accountID: detail.accountID, - text: displayName, - firstName: lodashGet(detail, 'firstName', ''), - lastName: lodashGet(detail, 'lastName', ''), - alternateText: LocalePhoneNumber.formatPhoneNumber(login) || displayName, - icons: [ - { - source: UserUtils.getAvatar(detail.avatar, detail.accountID), - name: login, - type: CONST.ICON_TYPE_AVATAR, - id: detail.accountID, - }, - ], - phoneNumber: lodashGet(detail, 'phoneNumber', ''), - selected: participant.selected, - }; - }); +function getParticipantsOption(participant, personalDetails) { + const detail = getPersonalDetailsForAccountIDs([participant.accountID], personalDetails)[participant.accountID]; + const login = detail.login || participant.login; + const displayName = detail.displayName || LocalePhoneNumber.formatPhoneNumber(login); + return { + keyForList: String(detail.accountID), + login, + accountID: detail.accountID, + text: displayName, + firstName: lodashGet(detail, 'firstName', ''), + lastName: lodashGet(detail, 'lastName', ''), + alternateText: LocalePhoneNumber.formatPhoneNumber(login) || displayName, + icons: [ + { + source: UserUtils.getAvatar(detail.avatar, detail.accountID), + name: login, + type: CONST.ICON_TYPE_AVATAR, + id: detail.accountID, + }, + ], + phoneNumber: lodashGet(detail, 'phoneNumber', ''), + selected: participant.selected, + }; } /** @@ -895,10 +890,10 @@ function getOptions( } // Always exclude already selected options and the currently logged in user - const loginOptionsToExclude = [...selectedOptions, {login: currentUserLogin}]; + const optionsToExclude = [...selectedOptions, {login: currentUserLogin}]; _.each(excludeLogins, (login) => { - loginOptionsToExclude.push({login}); + optionsToExclude.push({login}); }); if (includeRecentReports) { @@ -919,7 +914,11 @@ function getOptions( } // If we're excluding threads, check the report to see if it has a single participant and if the participant is already selected - if (!includeThreads && reportOption.login && _.some(loginOptionsToExclude, (option) => option.login === reportOption.login)) { + if ( + !includeThreads && + (reportOption.login || reportOption.reportID) && + _.some(optionsToExclude, (option) => (option.login && option.login === reportOption.login) || (option.reportID && option.reportID === reportOption.reportID)) + ) { continue; } @@ -943,7 +942,7 @@ function getOptions( // Add this login to the exclude list so it won't appear when we process the personal details if (reportOption.login) { - loginOptionsToExclude.push({login: reportOption.login}); + optionsToExclude.push({login: reportOption.login}); } } } @@ -951,7 +950,7 @@ function getOptions( if (includePersonalDetails) { // Next loop over all personal details removing any that are selectedUsers or recentChats _.each(allPersonalDetailsOptions, (personalDetailOption) => { - if (_.some(loginOptionsToExclude, (loginOptionToExclude) => loginOptionToExclude.login === personalDetailOption.login)) { + if (_.some(optionsToExclude, (optionToExclude) => optionToExclude.login === personalDetailOption.login)) { return; } const {searchText, participantsList, isChatRoom} = personalDetailOption; @@ -979,7 +978,7 @@ function getOptions( _.every(selectedOptions, (option) => option.login !== searchValue) && ((Str.isValidEmail(searchValue) && !Str.isDomainEmail(searchValue) && !Str.endsWith(searchValue, CONST.SMS.DOMAIN)) || (parsedPhoneNumber.possible && Str.isValidPhone(LoginUtils.getPhoneNumberWithoutSpecialChars(parsedPhoneNumber.number.input)))) && - !_.find(loginOptionsToExclude, (loginOptionToExclude) => loginOptionToExclude.login === addSMSDomainIfPhoneNumber(searchValue).toLowerCase()) && + !_.find(optionsToExclude, (optionToExclude) => optionToExclude.login === addSMSDomainIfPhoneNumber(searchValue).toLowerCase()) && (searchValue !== CONST.EMAIL.CHRONOS || Permissions.canUseChronos(betas)) && !excludeUnknownUsers ) { @@ -1322,8 +1321,8 @@ export { getIOUConfirmationOptionsFromParticipants, getSearchText, getAllReportErrors, - getPolicyExpenseReportOptions, - getParticipantsOptions, + getPolicyExpenseReportOption, + getParticipantsOption, isSearchStringMatch, shouldOptionShowTooltip, getLastMessageTextForReport, diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index d90a6cbf0e37..8f352c182171 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -1,34 +1,11 @@ -import lodashGet from 'lodash/get'; -import _ from 'underscore'; import Str from 'expensify-common/lib/str'; import * as FileUtils from './fileDownload/FileUtils'; import CONST from '../CONST'; -import Receipt from './actions/Receipt'; import ReceiptHTML from '../../assets/images/receipt-html.png'; import ReceiptDoc from '../../assets/images/receipt-doc.png'; import ReceiptGeneric from '../../assets/images/receipt-generic.png'; import ReceiptSVG from '../../assets/images/receipt-svg.png'; -function validateReceipt(file) { - const {fileExtension} = FileUtils.splitExtensionFromFileName(lodashGet(file, 'name', '')); - if (_.contains(CONST.API_ATTACHMENT_VALIDATIONS.UNALLOWED_EXTENSIONS, fileExtension.toLowerCase())) { - Receipt.setUploadReceiptError(true, 'attachmentPicker.wrongFileType', 'attachmentPicker.notAllowedExtension'); - return false; - } - - if (lodashGet(file, 'size', 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { - Receipt.setUploadReceiptError(true, 'attachmentPicker.attachmentTooLarge', 'attachmentPicker.sizeExceeded'); - return false; - } - - if (lodashGet(file, 'size', 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { - Receipt.setUploadReceiptError(true, 'attachmentPicker.attachmentTooSmall', 'attachmentPicker.sizeNotMet'); - return false; - } - - return true; -} - /** * Grab the appropriate receipt image and thumbnail URIs based on file type * @@ -64,4 +41,5 @@ function getThumbnailAndImageURIs(path, filename) { return {thumbnail: null, image}; } -export {validateReceipt, getThumbnailAndImageURIs}; +// eslint-disable-next-line import/prefer-default-export +export {getThumbnailAndImageURIs}; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 8e9cea908f74..2475dd40488c 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -248,7 +248,7 @@ function isReportManager(report) { * @returns {Boolean} */ function isReportApproved(report) { - return report && report.statusNum === CONST.REPORT.STATE_NUM.SUBMITTED && report.statusNum === CONST.REPORT.STATUS.APPROVED; + return report && report.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report.statusNum === CONST.REPORT.STATUS.APPROVED; } /** @@ -843,8 +843,30 @@ function hasAutomatedExpensifyAccountIDs(accountIDs) { * @returns {Array} */ function getReportRecipientAccountIDs(report, currentLoginAccountID) { - const participantAccountIDs = isTaskReport(report) ? [report.managerID] : lodashGet(report, 'participantAccountIDs'); - const reportParticipants = _.without(participantAccountIDs, currentLoginAccountID); + let finalReport = report; + // In 1:1 chat threads, the participants will be the same as parent report. If a report is specifically a 1:1 chat thread then we will + // get parent report and use its participants array. + if (isThread(report) && !(isTaskReport(report) || isMoneyRequestReport(report))) { + const parentReport = lodashGet(allReports, [`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`]); + if (hasSingleParticipant(parentReport)) { + finalReport = parentReport; + } + } + + let finalParticipantAccountIDs = []; + if (isMoneyRequestReport(report)) { + // For money requests i.e the IOU (1:1 person) and Expense (1:* person) reports, use the full `initialParticipantAccountIDs` array + // and add the `ownerAccountId`. Money request reports don't add `ownerAccountId` in `participantAccountIDs` array + finalParticipantAccountIDs = _.union(lodashGet(finalReport, 'participantAccountIDs'), [report.ownerAccountID]); + } else if (isTaskReport(report)) { + // Task reports `managerID` will change when assignee is changed, in that case the old `managerID` is still present in `participantAccountIDs` + // array along with the new one. We only need the `managerID` as a participant here. + finalParticipantAccountIDs = [report.managerID]; + } else { + finalParticipantAccountIDs = lodashGet(finalReport, 'participantAccountIDs'); + } + + const reportParticipants = _.without(finalParticipantAccountIDs, currentLoginAccountID); const participantsWithoutExpensifyAccountIDs = _.difference(reportParticipants, CONST.EXPENSIFY_ACCOUNT_IDS); return participantsWithoutExpensifyAccountIDs; } @@ -1470,7 +1492,7 @@ function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceip ) { translatePhraseKey = 'iou.paidWithExpensifyWithAmount'; } - return Localize.translateLocal(translatePhraseKey, {amount: formattedAmount}); + return Localize.translateLocal(translatePhraseKey, {amount: formattedAmount, payer: payerName}); } if (report.isWaitingOnBankAccount) { diff --git a/src/libs/RequestThrottle.ts b/src/libs/RequestThrottle.ts index 8f9a85dcedb5..d6ccab91bf23 100644 --- a/src/libs/RequestThrottle.ts +++ b/src/libs/RequestThrottle.ts @@ -16,8 +16,12 @@ function getRequestWaitTime() { return requestWaitTime; } +function getLastRequestWaitTime() { + return requestWaitTime; +} + function sleep(): Promise { return new Promise((resolve) => setTimeout(resolve, getRequestWaitTime())); } -export {clear, getRequestWaitTime, sleep}; +export {clear, getRequestWaitTime, sleep, getLastRequestWaitTime}; diff --git a/src/libs/SessionUtils.js b/src/libs/SessionUtils.ts similarity index 79% rename from src/libs/SessionUtils.js rename to src/libs/SessionUtils.ts index 7b1fc9f42d25..fcbc9a887fa9 100644 --- a/src/libs/SessionUtils.js +++ b/src/libs/SessionUtils.ts @@ -1,16 +1,10 @@ import Onyx from 'react-native-onyx'; -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import ONYXKEYS from '../ONYXKEYS'; /** * Determine if the transitioning user is logging in as a new user. - * - * @param {String} transitionURL - * @param {String} sessionEmail - * @returns {Boolean} */ -function isLoggingInAsNewUser(transitionURL, sessionEmail) { +function isLoggingInAsNewUser(transitionURL: string, sessionEmail: string): boolean { // The OldDot mobile app does not URL encode the parameters, but OldDot web // does. We don't want to deploy OldDot mobile again, so as a work around we // compare the session email to both the decoded and raw email from the transition link. @@ -27,22 +21,22 @@ function isLoggingInAsNewUser(transitionURL, sessionEmail) { // Capture the un-encoded text in the email param const emailParamRegex = /[?&]email=([^&]*)/g; const matches = emailParamRegex.exec(transitionURL); - const linkedEmail = lodashGet(matches, 1, null); + const linkedEmail = matches?.[1] ?? null; return linkedEmail !== sessionEmail; } -let loggedInDuringSession; +let loggedInDuringSession: boolean | undefined; // To tell if the user logged in during this session we will check the value of session.authToken once when the app's JS inits. When the user logs out // we can reset this flag so that it can be updated again. Onyx.connect({ key: ONYXKEYS.SESSION, callback: (session) => { - if (!_.isUndefined(loggedInDuringSession)) { + if (loggedInDuringSession) { return; } - if (session && session.authToken) { + if (session?.authToken) { loggedInDuringSession = false; } else { loggedInDuringSession = true; @@ -54,9 +48,6 @@ function resetDidUserLogInDuringSession() { loggedInDuringSession = undefined; } -/** - * @returns {boolean} - */ function didUserLogInDuringSession() { return Boolean(loggedInDuringSession); } diff --git a/src/libs/ValidationUtils.js b/src/libs/ValidationUtils.js index 7aded82fb0a9..a85a623bd3ec 100644 --- a/src/libs/ValidationUtils.js +++ b/src/libs/ValidationUtils.js @@ -314,6 +314,10 @@ function isValidValidateCode(validateCode) { return validateCode.match(CONST.VALIDATE_CODE_REGEX_STRING); } +function isValidRecoveryCode(recoveryCode) { + return recoveryCode.match(CONST.RECOVERY_CODE_REGEX_STRING); +} + /** * @param {String} code * @returns {Boolean} @@ -484,4 +488,5 @@ export { doesContainReservedWord, isNumeric, isValidAccountRoute, + isValidRecoveryCode, }; diff --git a/src/libs/Visibility/index.desktop.js b/src/libs/Visibility/index.desktop.ts similarity index 73% rename from src/libs/Visibility/index.desktop.js rename to src/libs/Visibility/index.desktop.ts index e3a7eab5bf56..bdc8b9a4267a 100644 --- a/src/libs/Visibility/index.desktop.js +++ b/src/libs/Visibility/index.desktop.ts @@ -1,31 +1,19 @@ import ELECTRON_EVENTS from '../../../desktop/ELECTRON_EVENTS'; +import {HasFocus, IsVisible, OnVisibilityChange} from './types'; /** * Detects whether the app is visible or not. Electron supports document.visibilityState, * but switching to another app while Electron is partially occluded will not trigger a state of hidden * so we ask the main process synchronously whether the BrowserWindow.isFocused() - * - * @returns {Boolean} */ -function isVisible() { - return window.electron.sendSync(ELECTRON_EVENTS.REQUEST_VISIBILITY); -} +const isVisible: IsVisible = () => !!window.electron.sendSync(ELECTRON_EVENTS.REQUEST_VISIBILITY); -/** - * @returns {Boolean} - */ -function hasFocus() { - return true; -} +const hasFocus: HasFocus = () => true; /** * Adds event listener for changes in visibility state - * - * @param {Function} callback - * - * @return {Function} removes the listener */ -function onVisibilityChange(callback) { +const onVisibilityChange: OnVisibilityChange = (callback) => { // Deliberately strip callback argument to be consistent across implementations window.electron.on(ELECTRON_EVENTS.FOCUS, () => callback()); window.electron.on(ELECTRON_EVENTS.BLUR, () => callback()); @@ -34,7 +22,7 @@ function onVisibilityChange(callback) { window.electron.removeAllListeners(ELECTRON_EVENTS.FOCUS); window.electron.removeAllListeners(ELECTRON_EVENTS.BLUR); }; -} +}; export default { isVisible, diff --git a/src/libs/Visibility/index.native.js b/src/libs/Visibility/index.native.ts similarity index 63% rename from src/libs/Visibility/index.native.js rename to src/libs/Visibility/index.native.ts index b888cf1a2b9f..695df3651da7 100644 --- a/src/libs/Visibility/index.native.js +++ b/src/libs/Visibility/index.native.ts @@ -2,32 +2,21 @@ // they do not use the Notification lib. import {AppState} from 'react-native'; +import {HasFocus, IsVisible, OnVisibilityChange} from './types'; -/** - * @return {Boolean} - */ -const isVisible = () => AppState.currentState === 'active'; +const isVisible: IsVisible = () => AppState.currentState === 'active'; -/** - * @returns {Boolean} - */ -function hasFocus() { - return true; -} +const hasFocus: HasFocus = () => true; /** * Adds event listener for changes in visibility state - * - * @param {Function} callback - * - * @return {Function} removes the listener */ -function onVisibilityChange(callback) { +const onVisibilityChange: OnVisibilityChange = (callback) => { // Deliberately strip callback argument to be consistent across implementations const subscription = AppState.addEventListener('change', () => callback()); return () => subscription.remove(); -} +}; export default { isVisible, diff --git a/src/libs/Visibility/index.js b/src/libs/Visibility/index.ts similarity index 61% rename from src/libs/Visibility/index.js rename to src/libs/Visibility/index.ts index 91a003168ccd..4cf18b010a07 100644 --- a/src/libs/Visibility/index.js +++ b/src/libs/Visibility/index.ts @@ -1,36 +1,25 @@ import {AppState} from 'react-native'; +import {HasFocus, IsVisible, OnVisibilityChange} from './types'; /** * Detects whether the app is visible or not. - * - * @returns {Boolean} */ -function isVisible() { - return document.visibilityState === 'visible'; -} +const isVisible: IsVisible = () => document.visibilityState === 'visible'; /** * Whether the app is focused. - * - * @returns {Boolean} */ -function hasFocus() { - return document.hasFocus(); -} +const hasFocus: HasFocus = () => document.hasFocus(); /** * Adds event listener for changes in visibility state - * - * @param {Function} callback - * - * @return {Function} removes the listener */ -function onVisibilityChange(callback) { +const onVisibilityChange: OnVisibilityChange = (callback) => { // Deliberately strip callback argument to be consistent across implementations const subscription = AppState.addEventListener('change', () => callback()); return () => subscription.remove(); -} +}; export default { isVisible, diff --git a/src/libs/Visibility/types.ts b/src/libs/Visibility/types.ts new file mode 100644 index 000000000000..0332f24740ce --- /dev/null +++ b/src/libs/Visibility/types.ts @@ -0,0 +1,5 @@ +type IsVisible = () => boolean; +type HasFocus = () => boolean; +type OnVisibilityChange = (callback: () => void) => () => void; + +export type {IsVisible, HasFocus, OnVisibilityChange}; diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index a9c3f8775cba..5753804eadfe 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -141,10 +141,11 @@ function getPolicyParamsForOpenOrReconnect() { /** * Returns the Onyx data that is used for both the OpenApp and ReconnectApp API commands. + * @param {Boolean} isOpenApp * @returns {Object} */ -function getOnyxDataForOpenOrReconnect() { - return { +function getOnyxDataForOpenOrReconnect(isOpenApp = false) { + const defaultData = { optimisticData: [ { onyxMethod: Onyx.METHOD.MERGE, @@ -167,6 +168,33 @@ function getOnyxDataForOpenOrReconnect() { }, ], }; + if (!isOpenApp) return defaultData; + return { + optimisticData: [ + ...defaultData.optimisticData, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_LOADING_APP, + value: true, + }, + ], + successData: [ + ...defaultData.successData, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_LOADING_APP, + value: false, + }, + ], + failureData: [ + ...defaultData.failureData, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.IS_LOADING_APP, + value: false, + }, + ], + }; } /** @@ -174,7 +202,7 @@ function getOnyxDataForOpenOrReconnect() { */ function openApp() { getPolicyParamsForOpenOrReconnect().then((policyParams) => { - API.read('OpenApp', policyParams, getOnyxDataForOpenOrReconnect()); + API.read('OpenApp', policyParams, getOnyxDataForOpenOrReconnect(true)); }); } @@ -291,7 +319,7 @@ function createWorkspaceAndNavigateToIt(policyOwnerEmail = '', makeMeAdmin = fal .then(() => { if (transitionFromOldDot) { // We must call goBack() to remove the /transition route from history - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); } if (shouldNavigateToAdminChat) { @@ -355,7 +383,7 @@ function setUpPoliciesAndNavigate(session, shouldNavigateToAdminChat) { Navigation.isNavigationReady() .then(() => { // We must call goBack() to remove the /transition route from history - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); Navigation.navigate(exitTo); }) .then(endSignOnTransition); @@ -371,7 +399,7 @@ function redirectThirdPartyDesktopSignIn() { if (url.pathname === `/${ROUTES.GOOGLE_SIGN_IN}` || url.pathname === `/${ROUTES.APPLE_SIGN_IN}`) { Navigation.isNavigationReady().then(() => { - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); Navigation.navigate(ROUTES.DESKTOP_SIGN_IN_REDIRECT); }); } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 587b2392deba..d9bdc3e2c043 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -562,8 +562,8 @@ function createDistanceRequest(report, participant, comment, created, transactio currency, created, merchant, - null, - null, + userAccountID, + currentUserEmail, optimisticReceipt, transactionID, category, @@ -676,9 +676,10 @@ function requestMoney(report, amount, currency, created, merchant, payeeEmail, p function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAccountID, amount, comment, currency, existingSplitChatReportID = '') { const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(currentUserLogin); const participantAccountIDs = _.map(participants, (participant) => Number(participant.accountID)); - const existingSplitChatReport = existingSplitChatReportID - ? allReports[`${ONYXKEYS.COLLECTION.REPORT}${existingSplitChatReportID}`] - : ReportUtils.getChatByParticipants(participantAccountIDs); + const existingSplitChatReport = + existingSplitChatReportID || participants[0].reportID + ? allReports[`${ONYXKEYS.COLLECTION.REPORT}${existingSplitChatReportID || participants[0].reportID}`] + : ReportUtils.getChatByParticipants(participantAccountIDs); const splitChatReport = existingSplitChatReport || ReportUtils.buildOptimisticChatReport(participantAccountIDs); const isOwnPolicyExpenseChat = splitChatReport.isOwnPolicyExpenseChat; @@ -1023,6 +1024,7 @@ function splitBillAndOpenReport(participants, currentUserLogin, currentUserAccou transactionID: splitData.transactionID, reportActionID: splitData.reportActionID, createdReportActionID: splitData.createdReportActionID, + policyID: splitData.policyID, }, onyxData, ); @@ -1145,7 +1147,9 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread.reportID}`, value: { - [updatedReportAction.reportActionID]: updatedReportAction, + [updatedReportAction.reportActionID]: { + errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericEditFailureMessage'), + }, }, }, { @@ -1391,14 +1395,14 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView // STEP 7: Navigate the user depending on which page they are on and which resources were deleted if (isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) { // Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report. - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); Navigation.navigate(ROUTES.getReportRoute(iouReport.reportID)); return; } if (shouldDeleteIOUReport) { // Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report. - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); Navigation.navigate(ROUTES.getReportRoute(iouReport.chatReportID)); } } @@ -1857,6 +1861,43 @@ function payMoneyRequest(paymentType, chatReport, iouReport) { Navigation.dismissModal(chatReport.reportID); } +/** + * @param {String} transactionID + * @param {Object} receipt + * @param {String} filePath + */ +function replaceReceipt(transactionID, receipt, filePath) { + const transaction = lodashGet(allTransactions, 'transactionID', {}); + const oldReceipt = lodashGet(transaction, 'receipt', {}); + + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: { + receipt: { + source: filePath, + state: CONST.IOU.RECEIPT_STATE.OPEN, + }, + filename: receipt.name, + }, + }, + ]; + + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: { + receipt: oldReceipt, + filename: transaction.filename, + }, + }, + ]; + + API.write('ReplaceReceipt', {transactionID, receipt}, {optimisticData, failureData}); +} + /** * Initialize money request info and navigate to the MoneyRequest page * @param {String} iouType @@ -2015,4 +2056,5 @@ export { setMoneyRequestReceipt, createEmptyTransaction, navigateToNextPage, + replaceReceipt, }; diff --git a/src/libs/actions/PaymentMethods.js b/src/libs/actions/PaymentMethods.js index 17fec65078ed..0ed6f8b036bb 100644 --- a/src/libs/actions/PaymentMethods.js +++ b/src/libs/actions/PaymentMethods.js @@ -18,12 +18,12 @@ const kycWallRef = createRef(); */ function continueSetup() { if (!kycWallRef.current || !kycWallRef.current.continue) { - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); return; } // Close the screen (Add Debit Card, Add Bank Account, or Enable Payments) on success and continue with setup - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); kycWallRef.current.continue(); } diff --git a/src/libs/actions/PersistedRequests.ts b/src/libs/actions/PersistedRequests.ts index dda88c465d77..040c7d3d87a8 100644 --- a/src/libs/actions/PersistedRequests.ts +++ b/src/libs/actions/PersistedRequests.ts @@ -10,8 +10,11 @@ Onyx.connect({ callback: (val) => (persistedRequests = val ?? []), }); +/** + * This promise is only used by tests. DO NOT USE THIS PROMISE IN THE APPLICATION CODE + */ function clear() { - Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, []); + return Onyx.set(ONYXKEYS.PERSISTED_REQUESTS, []); } function save(requestsToPersist: Request[]) { diff --git a/src/libs/actions/Receipt.ts b/src/libs/actions/Receipt.ts deleted file mode 100644 index 530db149d902..000000000000 --- a/src/libs/actions/Receipt.ts +++ /dev/null @@ -1,29 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '../../ONYXKEYS'; - -/** - * Sets the upload receipt error modal content when an invalid receipt is uploaded - */ -function setUploadReceiptError(isAttachmentInvalid: boolean, attachmentInvalidReasonTitle: string, attachmentInvalidReason: string) { - Onyx.merge(ONYXKEYS.RECEIPT_MODAL, { - isAttachmentInvalid, - attachmentInvalidReasonTitle, - attachmentInvalidReason, - }); -} - -/** - * Clears the receipt error modal - */ -function clearUploadReceiptError() { - Onyx.merge(ONYXKEYS.RECEIPT_MODAL, { - isAttachmentInvalid: false, - attachmentInvalidReasonTitle: '', - attachmentInvalidReason: '', - }); -} - -export default { - setUploadReceiptError, - clearUploadReceiptError, -}; diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 2a34c839a94e..55b03110e925 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1241,7 +1241,7 @@ function updateNotificationPreferenceAndNavigate(reportID, previousValue, newVal function updateWelcomeMessage(reportID, previousValue, newValue) { // No change needed, navigate back if (previousValue === newValue) { - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); return; } @@ -1261,7 +1261,7 @@ function updateWelcomeMessage(reportID, previousValue, newValue) { }, ]; API.write('UpdateWelcomeMessage', {reportID, welcomeMessage: parsedWelcomeMessage}, {optimisticData, failureData}); - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); } /** @@ -1438,7 +1438,7 @@ function deleteReport(reportID) { */ function navigateToConciergeChatAndDeleteReport(reportID) { // Dismiss the current report screen and replace it with Concierge Chat - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); navigateToConciergeChat(); deleteReport(reportID); } @@ -1820,7 +1820,11 @@ function leaveRoom(reportID) { ); Navigation.dismissModal(); if (Navigation.getTopmostReportId() === reportID) { - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); + } + if (report.parentReportID) { + Navigation.navigate(ROUTES.getReportRoute(report.parentReportID), CONST.NAVIGATION.TYPE.FORCED_UP); + return; } navigateToConciergeChat(); } diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 8f6c7861d8aa..fb6b3fe2bb12 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -785,7 +785,6 @@ function updateTheme(theme) { * @param {Object} status * @param {String} status.text * @param {String} status.emojiCode - * @param {String} status.clearAfter - ISO 8601 format string, which represents the time when the status should be cleared */ function updateCustomStatus(status) { API.write('UpdateStatus', status, { diff --git a/src/libs/focusWithDelay.js b/src/libs/focusWithDelay.ts similarity index 72% rename from src/libs/focusWithDelay.js rename to src/libs/focusWithDelay.ts index 367cc2b92f9f..00d0e915879e 100644 --- a/src/libs/focusWithDelay.js +++ b/src/libs/focusWithDelay.ts @@ -1,15 +1,14 @@ -import {InteractionManager} from 'react-native'; +import {InteractionManager, TextInput} from 'react-native'; import ComposerFocusManager from './ComposerFocusManager'; +type FocusWithDelay = (shouldDelay?: boolean) => void; /** * Create a function that focuses a text input. - * @param {Object} textInput the text input to focus - * @returns {Function} a function that focuses the text input with a configurable delay */ -function focusWithDelay(textInput) { +function focusWithDelay(textInput: TextInput | null): FocusWithDelay { /** * Focus the text input - * @param {Boolean} [shouldDelay=false] Impose delay before focusing the text input + * @param [shouldDelay] Impose delay before focusing the text input */ return (shouldDelay = false) => { // There could be other animations running while we trigger manual focus. @@ -18,6 +17,7 @@ function focusWithDelay(textInput) { if (!textInput) { return; } + if (!shouldDelay) { textInput.focus(); return; diff --git a/src/libs/migrateOnyx.js b/src/libs/migrateOnyx.js index 9389a9b66fbc..e691ea22ba79 100644 --- a/src/libs/migrateOnyx.js +++ b/src/libs/migrateOnyx.js @@ -6,7 +6,6 @@ import RenamePriorityModeKey from './migrations/RenamePriorityModeKey'; import MoveToIndexedDB from './migrations/MoveToIndexedDB'; import RenameExpensifyNewsStatus from './migrations/RenameExpensifyNewsStatus'; import AddLastVisibleActionCreated from './migrations/AddLastVisibleActionCreated'; -import KeyReportActionsByReportActionID from './migrations/KeyReportActionsByReportActionID'; import PersonalDetailsByAccountID from './migrations/PersonalDetailsByAccountID'; export default function () { @@ -22,7 +21,6 @@ export default function () { AddEncryptedAuthToken, RenameExpensifyNewsStatus, AddLastVisibleActionCreated, - KeyReportActionsByReportActionID, PersonalDetailsByAccountID, ]; diff --git a/src/libs/migrations/KeyReportActionsByReportActionID.js b/src/libs/migrations/KeyReportActionsByReportActionID.js deleted file mode 100644 index 0dc1d003feab..000000000000 --- a/src/libs/migrations/KeyReportActionsByReportActionID.js +++ /dev/null @@ -1,64 +0,0 @@ -import _ from 'underscore'; -import Onyx from 'react-native-onyx'; -import Log from '../Log'; -import ONYXKEYS from '../../ONYXKEYS'; - -/** - * This migration updates reportActions data to be keyed by reportActionID rather than by sequenceNumber. - * - * @returns {Promise} - */ -export default function () { - return new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - waitForCollectionCallback: true, - callback: (allReportActions) => { - Onyx.disconnect(connectionID); - - if (!allReportActions) { - Log.info('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because there were no reportActions'); - return resolve(); - } - - const newReportActions = {}; - const allReportActionsEntires = Object.entries(allReportActions); - for (let i = 0; i < allReportActionsEntires.length; i++) { - const [onyxKey, reportActionsForReport] = allReportActionsEntires[i]; - if (reportActionsForReport) { - const newReportActionsForReport = {}; - const reportActionsForReportEntries = Object.entries(reportActionsForReport); - for (let j = 0; j < reportActionsForReportEntries.length; j++) { - const [reportActionKey, reportAction] = reportActionsForReportEntries[j]; - if (!reportAction) { - Log.info('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because the reportAction was deleted'); - return resolve(); - } - if ( - !_.isNaN(Number(reportActionKey)) && - Number(reportActionKey) === Number(reportAction.reportActionID) && - Number(reportActionKey) !== Number(reportAction.sequenceNumber) - ) { - Log.info('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because we already migrated it'); - return resolve(); - } - - // Move it to be keyed by reportActionID instead - newReportActionsForReport[reportAction.reportActionID] = reportAction; - } - newReportActions[onyxKey] = newReportActionsForReport; - } - } - - if (_.isEmpty(newReportActions)) { - Log.info('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because there are no actions to migrate'); - return resolve(); - } - - Log.info(`[Migrate Onyx] Re-keying reportActions by reportActionID for ${_.keys(newReportActions).length} reports`); - // eslint-disable-next-line rulesdir/prefer-actions-set-data - return Onyx.multiSet(newReportActions).then(resolve); - }, - }); - }); -} diff --git a/src/pages/AddPersonalBankAccountPage.js b/src/pages/AddPersonalBankAccountPage.js index 8ef8b71b90d0..98bc09a7a217 100644 --- a/src/pages/AddPersonalBankAccountPage.js +++ b/src/pages/AddPersonalBankAccountPage.js @@ -132,7 +132,7 @@ class AddPersonalBankAccountPage extends React.Component { this.setState({selectedPlaidAccountID}); }} plaidData={this.props.plaidData} - onExitPlaid={Navigation.goBack} + onExitPlaid={() => Navigation.goBack(ROUTES.HOME)} receivedRedirectURI={getPlaidOAuthReceivedRedirectURI()} selectedPlaidAccountID={this.state.selectedPlaidAccountID} /> diff --git a/src/pages/ConciergePage.js b/src/pages/ConciergePage.js index 610fa3587b7a..e8509024b469 100644 --- a/src/pages/ConciergePage.js +++ b/src/pages/ConciergePage.js @@ -7,6 +7,7 @@ import ONYXKEYS from '../ONYXKEYS'; import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator'; import Navigation from '../libs/Navigation/Navigation'; import * as Report from '../libs/actions/Report'; +import ROUTES from '../ROUTES'; const propTypes = { /** Session info for the currently logged in user. */ @@ -31,7 +32,7 @@ function ConciergePage(props) { useFocusEffect(() => { if (_.has(props.session, 'authToken')) { // Pop the concierge loading page before opening the concierge report. - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); Report.navigateToConciergeChat(); } else { Navigation.navigate(); diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js index 7873c4daa00c..52d5fe4a7842 100755 --- a/src/pages/DetailsPage.js +++ b/src/pages/DetailsPage.js @@ -27,8 +27,6 @@ import * as Report from '../libs/actions/Report'; import OfflineWithFeedback from '../components/OfflineWithFeedback'; import AutoUpdateTime from '../components/AutoUpdateTime'; import FullPageNotFoundView from '../components/BlockingViews/FullPageNotFoundView'; -import Navigation from '../libs/Navigation/Navigation'; -import ROUTES from '../ROUTES'; import * as UserUtils from '../libs/UserUtils'; const matchType = PropTypes.shape({ @@ -130,10 +128,7 @@ function DetailsPage(props) { return ( - Navigation.goBack(ROUTES.HOME)} - /> + - + - +
descriptionInputRef.current && descriptionInputRef.current.focus()} > - Navigation.goBack()} - /> + merchantInputRef.current && merchantInputRef.current.focus()} > - + + ); + } + return ; } diff --git a/src/pages/EditRequestReceiptPage.js b/src/pages/EditRequestReceiptPage.js new file mode 100644 index 000000000000..47aa23a93432 --- /dev/null +++ b/src/pages/EditRequestReceiptPage.js @@ -0,0 +1,52 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ScreenWrapper from '../components/ScreenWrapper'; +import HeaderWithBackButton from '../components/HeaderWithBackButton'; +import Navigation from '../libs/Navigation/Navigation'; +import useLocalize from '../hooks/useLocalize'; +import ReceiptSelector from './iou/ReceiptSelector'; +import DragAndDropProvider from '../components/DragAndDrop/Provider'; + +const propTypes = { + /** React Navigation route */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** The type of IOU report, i.e. bill, request, send */ + iouType: PropTypes.string, + + /** The report ID of the IOU */ + reportID: PropTypes.string, + }), + }).isRequired, + + /** The id of the transaction we're editing */ + transactionID: PropTypes.string.isRequired, +}; + +function EditRequestReceiptPage({route, transactionID}) { + const {translate} = useLocalize(); + + return ( + + + + + + + ); +} + +EditRequestReceiptPage.propTypes = propTypes; +EditRequestReceiptPage.displayName = 'EditRequestReceiptPage'; + +export default EditRequestReceiptPage; diff --git a/src/pages/EnablePayments/OnfidoStep.js b/src/pages/EnablePayments/OnfidoStep.js index 31727f51cb69..b7181295ab77 100644 --- a/src/pages/EnablePayments/OnfidoStep.js +++ b/src/pages/EnablePayments/OnfidoStep.js @@ -13,6 +13,7 @@ import Growl from '../../libs/Growl'; import OnfidoPrivacy from './OnfidoPrivacy'; import walletOnfidoDataPropTypes from './walletOnfidoDataPropTypes'; import FullPageOfflineBlockingView from '../../components/BlockingViews/FullPageOfflineBlockingView'; +import ROUTES from '../../ROUTES'; const propTypes = { /** Stores various information used to build the UI and call any APIs */ @@ -51,7 +52,7 @@ class OnfidoStep extends React.Component { Growl.error(this.props.translate('onfidoStep.genericError'), 10000); }} onUserExit={() => { - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); }} onSuccess={(data) => { BankAccounts.verifyIdentity({ diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index e72cb9a3f79b..edbae46a3207 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -55,8 +55,9 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate}) const headerMessage = OptionsListUtils.getHeaderMessage( filteredPersonalDetails.length + filteredRecentReports.length !== 0, Boolean(filteredUserToInvite), - searchTerm, + searchTerm.trim(), maxParticipantsReached, + _.some(selectedOptions, (participant) => participant.searchText.toLowerCase().includes(searchTerm.trim().toLowerCase())), ); const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails); diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.js b/src/pages/PrivateNotes/PrivateNotesEditPage.js index 4cada83941ac..206e9e74d91f 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.js +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.js @@ -24,6 +24,7 @@ import * as Report from '../../libs/actions/Report'; import useLocalize from '../../hooks/useLocalize'; import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import focusAndUpdateMultilineInputRange from '../../libs/focusAndUpdateMultilineInputRange'; +import ROUTES from '../../ROUTES'; const propTypes = { /** All of the personal details for everyone */ @@ -72,7 +73,7 @@ function PrivateNotesEditPage({route, personalDetailsList, session, report}) { Keyboard.dismiss(); // Take user back to the PrivateNotesView page - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); }; return ( @@ -83,14 +84,12 @@ function PrivateNotesEditPage({route, personalDetailsList, session, report}) { Navigation.goBack()} > Navigation.dismissModal()} - onBackButtonPress={() => Navigation.goBack()} /> diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.js b/src/pages/PrivateNotes/PrivateNotesListPage.js index 5ea081a12f25..098bfd2a245b 100644 --- a/src/pages/PrivateNotes/PrivateNotesListPage.js +++ b/src/pages/PrivateNotes/PrivateNotesListPage.js @@ -107,26 +107,22 @@ function PrivateNotesListPage({report, personalDetailsList, network, session}) { const privateNoteBrickRoadIndicator = (accountID) => (!_.isEmpty(lodashGet(report, ['privateNotes', accountID, 'errors'], '')) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''); return _.chain(lodashGet(report, 'privateNotes', {})) .map((privateNote, accountID) => ({ - title: Number(lodashGet(session, 'accountID', null)) === Number(accountID) ? 'My note' : lodashGet(personalDetailsList, [accountID, 'login'], ''), + title: Number(lodashGet(session, 'accountID', null)) === Number(accountID) ? translate('privateNotes.myNote') : lodashGet(personalDetailsList, [accountID, 'login'], ''), icon: UserUtils.getAvatar(lodashGet(personalDetailsList, [accountID, 'avatar'], UserUtils.getDefaultAvatar(accountID)), accountID), iconType: CONST.ICON_TYPE_AVATAR, action: () => Navigation.navigate(ROUTES.getPrivateNotesViewRoute(report.reportID, accountID)), brickRoadIndicator: privateNoteBrickRoadIndicator(accountID), })) .value(); - }, [report, personalDetailsList, session]); + }, [report, personalDetailsList, session, translate]); return ( - Navigation.goBack()} - > + Navigation.dismissModal()} - onBackButtonPress={() => Navigation.goBack()} /> {report.isLoadingPrivateNotes && _.isEmpty(lodashGet(report, 'privateNotes', {})) ? ( diff --git a/src/pages/PrivateNotes/PrivateNotesViewPage.js b/src/pages/PrivateNotes/PrivateNotesViewPage.js index 86814ed4dc92..48f053f10f90 100644 --- a/src/pages/PrivateNotes/PrivateNotesViewPage.js +++ b/src/pages/PrivateNotes/PrivateNotesViewPage.js @@ -60,14 +60,12 @@ function PrivateNotesViewPage({route, personalDetailsList, session, report}) { Navigation.goBack()} > Navigation.dismissModal()} - onBackButtonPress={() => Navigation.goBack()} /> diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 19f2b1fdc0c6..b515da04b7be 100755 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -139,7 +139,7 @@ function ProfilePage(props) { const hasStatus = !!statusEmojiCode && Permissions.canUseCustomStatus(props.betas); const statusContent = `${statusEmojiCode} ${statusText}`; - const navigateBackTo = lodashGet(props.route, 'params.backTo', ''); + const navigateBackTo = lodashGet(props.route, 'params.backTo', ROUTES.HOME); const chatReportWithCurrentUser = !isCurrentUser && !Session.isAnonymousUser() ? ReportUtils.getChatByParticipants([accountID]) : 0; diff --git a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js index 80c9257d367a..70cf43fcfdc2 100644 --- a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js +++ b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js @@ -1,4 +1,5 @@ -import React, {useCallback} from 'react'; +import React, {useCallback, useEffect} from 'react'; +import {useIsFocused} from '@react-navigation/native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import lodashGet from 'lodash/get'; @@ -41,6 +42,7 @@ const defaultProps = { function BankAccountPlaidStep(props) { const {plaidData, receivedRedirectURI, plaidLinkOAuthToken, reimbursementAccount, reimbursementAccountDraft, onBackButtonPress, getDefaultStateForField, translate} = props; + const isFocused = useIsFocused(); const validate = useCallback((values) => { const errorFields = {}; @@ -51,6 +53,14 @@ function BankAccountPlaidStep(props) { return errorFields; }, []); + useEffect(() => { + const plaidBankAccounts = lodashGet(plaidData, 'bankAccounts') || []; + if (isFocused || plaidBankAccounts.length) { + return; + } + BankAccounts.setBankAccountSubStep(null); + }, [isFocused, plaidData]); + const submit = useCallback(() => { const selectedPlaidBankAccount = _.findWhere(lodashGet(plaidData, 'bankAccounts', []), { plaidAccountID: lodashGet(reimbursementAccountDraft, 'plaidAccountID', ''), @@ -103,7 +113,6 @@ function BankAccountPlaidStep(props) { }} plaidData={plaidData} onExitPlaid={() => BankAccounts.setBankAccountSubStep(null)} - onBlurPlaid={() => BankAccounts.setBankAccountSubStep(null)} receivedRedirectURI={receivedRedirectURI} plaidLinkOAuthToken={plaidLinkOAuthToken} allowDebit diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index 3160ad590c50..cdb3aeebe924 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -284,7 +284,7 @@ class ReimbursementAccountPage extends React.Component { const currentStep = achData.currentStep || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; const subStep = achData.subStep; const shouldShowOnfido = this.props.onfidoToken && !achData.isOnfidoSetupComplete; - const backTo = lodashGet(this.props.route.params, 'backTo'); + const backTo = lodashGet(this.props.route.params, 'backTo', ROUTES.HOME); switch (currentStep) { case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: if (this.hasInProgressVBBA()) { @@ -405,7 +405,7 @@ class ReimbursementAccountPage extends React.Component { continue={this.continue} policyName={policyName} onBackButtonPress={() => { - Navigation.goBack(lodashGet(this.props.route.params, 'backTo')); + Navigation.goBack(lodashGet(this.props.route.params, 'backTo', ROUTES.HOME)); }} /> ); diff --git a/src/pages/TeachersUnite/SaveTheWorldPage.js b/src/pages/TeachersUnite/SaveTheWorldPage.js index 47c441ad934c..1fb863051da5 100644 --- a/src/pages/TeachersUnite/SaveTheWorldPage.js +++ b/src/pages/TeachersUnite/SaveTheWorldPage.js @@ -53,7 +53,7 @@ function SaveTheWorldPage(props) { Navigation.goBack(ROUTES.HOME)} backgroundColor={themeColors.PAGE_BACKGROUND_COLORS[ROUTES.I_KNOW_A_TEACHER]} illustration={LottieAnimations.SaveTheWorld} > diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 8528b8f213a9..63e60a545de9 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -328,7 +328,6 @@ function ReportScreen({ subtitleKey="notFound.noAccess" shouldShowCloseButton={false} shouldShowBackButton={isSmallScreenWidth} - onBackButtonPress={Navigation.goBack} shouldShowLink={false} > )} diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 9ff787ebe21b..4f761e92eaf5 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -3,6 +3,7 @@ import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; +import lodashGet from 'lodash/get'; import ONYXKEYS from '../../../../ONYXKEYS'; import styles from '../../../../styles/styles'; import OptionsSelector from '../../../../components/OptionsSelector'; @@ -58,6 +59,9 @@ const propTypes = { /** Whether the money request is a distance request or not */ isDistanceRequest: PropTypes.bool, + /** Whether the money request is a scan request or not */ + isScanRequest: PropTypes.bool, + ...withLocalizePropTypes, }; @@ -69,6 +73,7 @@ const defaultProps = { reports: {}, betas: [], isDistanceRequest: false, + isScanRequest: false, }; function MoneyRequestParticipantsSelector({ @@ -84,6 +89,7 @@ function MoneyRequestParticipantsSelector({ safeAreaPaddingBottomStyle, iouType, isDistanceRequest, + isScanRequest, }) { const [searchTerm, setSearchTerm] = useState(''); const [newChatOptions, setNewChatOptions] = useState({ @@ -105,7 +111,10 @@ function MoneyRequestParticipantsSelector({ newSections.push({ title: undefined, - data: OptionsListUtils.getParticipantsOptions(participants, personalDetails), + data: _.map(participants, (participant) => { + const isPolicyExpenseChat = lodashGet(participant, 'isPolicyExpenseChat', false); + return isPolicyExpenseChat ? OptionsListUtils.getPolicyExpenseReportOption(participant) : OptionsListUtils.getParticipantsOption(participant, personalDetails); + }), shouldShow: true, indexOffset, }); @@ -159,14 +168,27 @@ function MoneyRequestParticipantsSelector({ */ const addParticipantToSelection = useCallback( (option) => { - const isOptionInList = _.some(participants, (selectedOption) => selectedOption.accountID === option.accountID); - + const isOptionSelected = (selectedOption) => { + if (selectedOption.accountID && selectedOption.accountID === option.accountID) { + return true; + } + + if (selectedOption.reportID && selectedOption.reportID === option.reportID) { + return true; + } + + return false; + }; + const isOptionInList = _.some(participants, isOptionSelected); let newSelectedOptions; if (isOptionInList) { - newSelectedOptions = _.reject(participants, (selectedOption) => selectedOption.accountID === option.accountID); + newSelectedOptions = _.reject(participants, isOptionSelected); } else { - newSelectedOptions = [...participants, {accountID: option.accountID, login: option.login, selected: true}]; + newSelectedOptions = [ + ...participants, + {accountID: option.accountID, login: option.login, isPolicyExpenseChat: option.isPolicyExpenseChat, reportID: option.reportID, selected: true}, + ]; } onAddParticipants(newSelectedOptions); @@ -199,8 +221,9 @@ function MoneyRequestParticipantsSelector({ const headerMessage = OptionsListUtils.getHeaderMessage( newChatOptions.personalDetails.length + newChatOptions.recentReports.length !== 0, Boolean(newChatOptions.userToInvite), - searchTerm, + searchTerm.trim(), maxParticipantsReached, + _.some(participants, (participant) => participant.login.toLowerCase().includes(searchTerm.trim().toLowerCase())), ); const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails); @@ -227,10 +250,17 @@ function MoneyRequestParticipantsSelector({ }); }, [betas, reports, participants, personalDetails, translate, searchTerm, setNewChatOptions, iouType, isDistanceRequest]); + // Right now you can't split a request with a workspace and other additional participants + // This is getting properly fixed in https://github.com/Expensify/App/issues/27508, but as a stop-gap to prevent + // the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants + const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat); + const shouldShowConfirmButton = !(participants.length > 1 && hasPolicyExpenseChatParticipant); + const isAllowedToSplit = !isDistanceRequest && !isScanRequest; + return ( 0 ? safeAreaPaddingBottomStyle : {}]}> { - Navigation.goBack(isEditing ? ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID) : null); + Navigation.goBack(isEditing ? ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID) : ROUTES.HOME); }; const navigateToCurrencySelectionPage = () => { diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js index f178b25fd0fb..dcd356978bd3 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -2,7 +2,6 @@ import React, {useMemo, useCallback, useEffect} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; -import moment from 'moment'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '../../../../components/withCurrentUserPersonalDetails'; import MenuItemWithTopDescription from '../../../../components/MenuItemWithTopDescription'; import StaticHeaderPageLayout from '../../../../components/StaticHeaderPageLayout'; @@ -44,8 +43,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { const navigateBackToSettingsPage = useCallback(() => Navigation.goBack(ROUTES.SETTINGS_PROFILE, false, true), []); const updateStatus = useCallback(() => { - const endOfDay = moment().endOf('day').format('YYYY-MM-DD HH:mm:ss'); - User.updateCustomStatus({text: defaultText, emojiCode: defaultEmoji, clearAfter: endOfDay}); + User.updateCustomStatus({text: defaultText, emojiCode: defaultEmoji}); User.clearDraftCustomStatus(); Navigation.goBack(ROUTES.SETTINGS_PROFILE); diff --git a/src/pages/settings/Profile/LoungeAccessPage.js b/src/pages/settings/Profile/LoungeAccessPage.js index ce047bd9ccae..c322c8e426f3 100644 --- a/src/pages/settings/Profile/LoungeAccessPage.js +++ b/src/pages/settings/Profile/LoungeAccessPage.js @@ -66,7 +66,7 @@ function LoungeAccessPage(props) { const overlayContent = () => ( diff --git a/src/pages/signin/SignInModal.js b/src/pages/signin/SignInModal.js index 0cd566a47327..50b0af0bfa05 100644 --- a/src/pages/signin/SignInModal.js +++ b/src/pages/signin/SignInModal.js @@ -23,7 +23,7 @@ function SignInModal() { includeSafeAreaPaddingBottom={false} shouldEnableMaxHeight > - Navigation.goBack()} /> + ); diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index 3e27e6cd253a..290c528672d2 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -1,4 +1,4 @@ -import React, {useEffect, useRef} from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import PropTypes from 'prop-types'; import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; @@ -87,6 +87,9 @@ function SignInPage({credentials, account, isInModal}) { const shouldShowSmallScreen = isSmallScreenWidth || isInModal; const safeAreaInsets = useSafeAreaInsets(); const signInPageLayoutRef = useRef(); + /** This state is needed to keep track of if user is using recovery code instead of 2fa code, + * and we need it here since welcome text(`welcomeText`) also depends on it */ + const [isUsingRecoveryCode, setIsUsingRecoveryCode] = useState(false); useEffect(() => Performance.measureTTI(), []); useEffect(() => { @@ -114,7 +117,7 @@ function SignInPage({credentials, account, isInModal}) { if (account.requiresTwoFactorAuth) { // We will only know this after a user signs in successfully, without their 2FA code welcomeHeader = isSmallScreenWidth ? '' : translate('welcomeText.welcomeBack'); - welcomeText = translate('validateCodeForm.enterAuthenticatorCode'); + welcomeText = isUsingRecoveryCode ? translate('validateCodeForm.enterRecoveryCode') : translate('validateCodeForm.enterAuthenticatorCode'); } else { const userLogin = Str.removeSMSDomain(credentials.login || ''); @@ -162,7 +165,12 @@ function SignInPage({credentials, account, isInModal}) { blurOnSubmit={account.validated === false} scrollPageToTop={signInPageLayoutRef.current && signInPageLayoutRef.current.scrollPageToTop} /> - {shouldShowValidateCodeForm && } + {shouldShowValidateCodeForm && ( + + )} {shouldShowUnlinkLoginForm && } {shouldShowEmailDeliveryFailurePage && } diff --git a/src/pages/signin/SignInPageLayout/BackgroundImage/index.android.js b/src/pages/signin/SignInPageLayout/BackgroundImage/index.android.js index ee6a2f1aa418..6ce60fc364fd 100644 --- a/src/pages/signin/SignInPageLayout/BackgroundImage/index.android.js +++ b/src/pages/signin/SignInPageLayout/BackgroundImage/index.android.js @@ -1,3 +1,19 @@ -import backgroundImage from '../../../../../assets/images/home-background--android.svg'; +import React from 'react'; +import AndroidBackgroundImage from '../../../../../assets/images/home-background--android.svg'; +import styles from '../../../../styles/styles'; +import defaultPropTypes from './propTypes'; -export default backgroundImage; +function BackgroundImage(props) { + return ( + + ); +} + +BackgroundImage.displayName = 'BackgroundImage'; +BackgroundImage.propTypes = defaultPropTypes; + +export default BackgroundImage; diff --git a/src/pages/signin/SignInPageLayout/BackgroundImage/index.js b/src/pages/signin/SignInPageLayout/BackgroundImage/index.js index d8c621509314..710f7b373a81 100644 --- a/src/pages/signin/SignInPageLayout/BackgroundImage/index.js +++ b/src/pages/signin/SignInPageLayout/BackgroundImage/index.js @@ -3,15 +3,17 @@ import PropTypes from 'prop-types'; import MobileBackgroundImage from '../../../../../assets/images/home-background--mobile.svg'; import DesktopBackgroundImage from '../../../../../assets/images/home-background--desktop.svg'; import styles from '../../../../styles/styles'; +import defaultPropTypes from './propTypes'; const defaultProps = { isSmallScreen: false, }; const propTypes = { + /** Is the window width narrow, like on a mobile device */ isSmallScreen: PropTypes.bool, - pointerEvents: PropTypes.string.isRequired, - width: PropTypes.number.isRequired, + + ...defaultPropTypes, }; function BackgroundImage(props) { return props.isSmallScreen ? ( diff --git a/src/pages/signin/SignInPageLayout/BackgroundImage/propTypes.js b/src/pages/signin/SignInPageLayout/BackgroundImage/propTypes.js new file mode 100644 index 000000000000..533d22ad12c5 --- /dev/null +++ b/src/pages/signin/SignInPageLayout/BackgroundImage/propTypes.js @@ -0,0 +1,11 @@ +import PropTypes from 'prop-types'; + +const propTypes = { + /** pointerEvents property to the SVG element */ + pointerEvents: PropTypes.string.isRequired, + + /** The width of the image. */ + width: PropTypes.number.isRequired, +}; + +export default propTypes; diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index 51cb287f9564..7815976609c5 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -26,6 +26,7 @@ import Terms from '../Terms'; import PressableWithFeedback from '../../../components/Pressable/PressableWithFeedback'; import usePrevious from '../../../hooks/usePrevious'; import * as StyleUtils from '../../../styles/StyleUtils'; +import TextInput from '../../../components/TextInput'; const propTypes = { /* Onyx Props */ @@ -60,6 +61,12 @@ const propTypes = { /** Specifies autocomplete hints for the system, so it can provide autofill */ autoComplete: PropTypes.oneOf(['sms-otp', 'one-time-code']).isRequired, + /** Determines if user is switched to using recovery code instead of 2fa code */ + isUsingRecoveryCode: PropTypes.bool.isRequired, + + /** Function to change `isUsingRecoveryCode` state when user toggles between 2fa code and recovery code */ + setIsUsingRecoveryCode: PropTypes.func.isRequired, + ...withLocalizePropTypes, }; @@ -77,6 +84,7 @@ function BaseValidateCodeForm(props) { const [validateCode, setValidateCode] = useState(props.credentials.validateCode || ''); const [twoFactorAuthCode, setTwoFactorAuthCode] = useState(''); const [timeRemaining, setTimeRemaining] = useState(30); + const [recoveryCode, setRecoveryCode] = useState(''); const prevRequiresTwoFactorAuth = usePrevious(props.account.requiresTwoFactorAuth); const prevValidateCode = usePrevious(props.credentials.validateCode); @@ -149,7 +157,17 @@ function BaseValidateCodeForm(props) { * @param {String} key */ const onTextInput = (text, key) => { - const setInput = key === 'validateCode' ? setValidateCode : setTwoFactorAuthCode; + let setInput; + if (key === 'validateCode') { + setInput = setValidateCode; + } + if (key === 'twoFactorAuthCode') { + setInput = setTwoFactorAuthCode; + } + if (key === 'recoveryCode') { + setInput = setRecoveryCode; + } + setInput(text); setFormError((prevError) => ({...prevError, [key]: ''})); @@ -174,6 +192,8 @@ function BaseValidateCodeForm(props) { setTwoFactorAuthCode(''); setFormError({}); setValidateCode(''); + props.setIsUsingRecoveryCode(false); + setRecoveryCode(''); }; /** @@ -184,11 +204,30 @@ function BaseValidateCodeForm(props) { Session.clearSignInData(); }; + /** + * Switches between 2fa and recovery code, clears inputs and errors + */ + const switchBetween2faAndRecoveryCode = () => { + props.setIsUsingRecoveryCode(!props.isUsingRecoveryCode); + + setRecoveryCode(''); + setTwoFactorAuthCode(''); + + setFormError((prevError) => ({...prevError, recoveryCode: '', twoFactorAuthCode: ''})); + + if (props.account.errors) { + Session.clearAccountMessages(); + } + }; + useEffect(() => { if (!isLoadingResendValidationForm) { return; } clearLocalSignInData(); + // `clearLocalSignInData` is not required as a dependency, and adding it + // overcomplicates things requiring clearLocalSignInData function to use useCallback + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isLoadingResendValidationForm]); /** @@ -203,13 +242,27 @@ function BaseValidateCodeForm(props) { if (input2FARef.current) { input2FARef.current.blur(); } - if (!twoFactorAuthCode.trim()) { - setFormError({twoFactorAuthCode: 'validateCodeForm.error.pleaseFillTwoFactorAuth'}); - return; - } - if (!ValidationUtils.isValidTwoFactorCode(twoFactorAuthCode)) { - setFormError({twoFactorAuthCode: 'passwordForm.error.incorrect2fa'}); - return; + /** + * User could be using either recovery code or 2fa code + */ + if (!props.isUsingRecoveryCode) { + if (!twoFactorAuthCode.trim()) { + setFormError({twoFactorAuthCode: 'validateCodeForm.error.pleaseFillTwoFactorAuth'}); + return; + } + if (!ValidationUtils.isValidTwoFactorCode(twoFactorAuthCode)) { + setFormError({twoFactorAuthCode: 'passwordForm.error.incorrect2fa'}); + return; + } + } else { + if (!recoveryCode.trim()) { + setFormError({recoveryCode: 'recoveryCodeForm.error.pleaseFillRecoveryCode'}); + return; + } + if (!ValidationUtils.isValidRecoveryCode(recoveryCode)) { + setFormError({recoveryCode: 'recoveryCodeForm.error.incorrectRecoveryCode'}); + return; + } } } else { if (inputValidateCodeRef.current) { @@ -226,33 +279,61 @@ function BaseValidateCodeForm(props) { } setFormError({}); + const recoveryCodeOr2faCode = props.isUsingRecoveryCode ? recoveryCode : twoFactorAuthCode; + const accountID = lodashGet(props.credentials, 'accountID'); if (accountID) { - Session.signInWithValidateCode(accountID, validateCode, props.preferredLocale, twoFactorAuthCode); + Session.signInWithValidateCode(accountID, validateCode, props.preferredLocale, recoveryCodeOr2faCode); } else { - Session.signIn(validateCode, twoFactorAuthCode, props.preferredLocale); + Session.signIn(validateCode, recoveryCodeOr2faCode, props.preferredLocale); } - }, [props.account, props.credentials, props.preferredLocale, twoFactorAuthCode, validateCode]); + }, [props.account, props.credentials, props.preferredLocale, twoFactorAuthCode, validateCode, props.isUsingRecoveryCode, recoveryCode]); return ( <> {/* At this point, if we know the account requires 2FA we already successfully authenticated */} {props.account.requiresTwoFactorAuth ? ( - onTextInput(text, 'twoFactorAuthCode')} - onFulfill={validateAndSubmitForm} - maxLength={CONST.TFA_CODE_LENGTH} - errorText={formError.twoFactorAuthCode ? props.translate(formError.twoFactorAuthCode) : ''} - hasError={hasError} - autoFocus - /> + {props.isUsingRecoveryCode ? ( + onTextInput(text, 'recoveryCode')} + maxLength={CONST.RECOVERY_CODE_LENGTH} + label={props.translate('recoveryCodeForm.recoveryCode')} + errorText={formError.recoveryCode ? props.translate(formError.recoveryCode) : ''} + hasError={hasError} + autoFocus + /> + ) : ( + onTextInput(text, 'twoFactorAuthCode')} + onFulfill={validateAndSubmitForm} + maxLength={CONST.TFA_CODE_LENGTH} + errorText={formError.twoFactorAuthCode ? props.translate(formError.twoFactorAuthCode) : ''} + hasError={hasError} + autoFocus + /> + )} {hasError && } + + {props.isUsingRecoveryCode ? props.translate('recoveryCodeForm.use2fa') : props.translate('recoveryCodeForm.useRecoveryCode')} + ) : ( diff --git a/src/pages/signin/ValidateCodeForm/index.android.js b/src/pages/signin/ValidateCodeForm/index.android.js index 7ff81357725d..1e888d24bc60 100644 --- a/src/pages/signin/ValidateCodeForm/index.android.js +++ b/src/pages/signin/ValidateCodeForm/index.android.js @@ -1,11 +1,24 @@ import React from 'react'; +import PropTypes from 'prop-types'; import BaseValidateCodeForm from './BaseValidateCodeForm'; const defaultProps = {}; -const propTypes = {}; -function ValidateCodeForm() { - return ; +const propTypes = { + /** Determines if user is switched to using recovery code instead of 2fa code */ + isUsingRecoveryCode: PropTypes.bool.isRequired, + + /** Function to change `isUsingRecoveryCode` state when user toggles between 2fa code and recovery code */ + setIsUsingRecoveryCode: PropTypes.func.isRequired, +}; +function ValidateCodeForm(props) { + return ( + + ); } ValidateCodeForm.displayName = 'ValidateCodeForm'; diff --git a/src/pages/signin/ValidateCodeForm/index.js b/src/pages/signin/ValidateCodeForm/index.js index 6b01c7d4dec2..540b6a3e3ed6 100644 --- a/src/pages/signin/ValidateCodeForm/index.js +++ b/src/pages/signin/ValidateCodeForm/index.js @@ -1,11 +1,24 @@ import React from 'react'; +import PropTypes from 'prop-types'; import BaseValidateCodeForm from './BaseValidateCodeForm'; const defaultProps = {}; -const propTypes = {}; -function ValidateCodeForm() { - return ; +const propTypes = { + /** Determines if user is switched to using recovery code instead of 2fa code */ + isUsingRecoveryCode: PropTypes.bool.isRequired, + + /** Function to change `isUsingRecoveryCode` state when user toggles between 2fa code and recovery code */ + setIsUsingRecoveryCode: PropTypes.func.isRequired, +}; +function ValidateCodeForm(props) { + return ( + + ); } ValidateCodeForm.displayName = 'ValidateCodeForm'; diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 9a046d7fe4c2..3baf84f54ccf 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -122,7 +122,7 @@ class WorkspaceInviteMessagePage extends React.Component { Policy.addMembersToWorkspace(this.props.invitedEmailsToAccountIDsDraft, this.state.welcomeNote, this.props.route.params.policyID); Policy.setWorkspaceInviteMembersDraft(this.props.route.params.policyID, {}); // Pop the invite message page before navigating to the members page. - Navigation.goBack(); + Navigation.goBack(ROUTES.HOME); Navigation.navigate(ROUTES.getWorkspaceMembersRoute(this.props.route.params.policyID)); } @@ -171,7 +171,6 @@ class WorkspaceInviteMessagePage extends React.Component { guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_MEMBERS} shouldShowBackButton onCloseButtonPress={() => Navigation.dismissModal()} - onBackButtonPress={() => Navigation.goBack()} /> ({ opacity: 0, }, + invisiblePopover: { + position: 'absolute', + opacity: 0, + left: -9999, + }, + containerWithSpaceBetween: { justifyContent: 'space-between', width: '100%', @@ -3181,6 +3187,11 @@ const styles = (theme) => ({ horizontal: windowWidth - 10, }), + threeDotsPopoverOffsetAttachmentModal: (windowWidth) => ({ + ...getPopOverVerticalOffset(80), + horizontal: windowWidth - 140, + }), + invert: { // It's important to invert the Y AND X axis to prevent a react native issue that can lead to ANRs on android 13 transform: [{scaleX: -1}, {scaleY: -1}], @@ -3614,10 +3625,8 @@ const styles = (theme) => ({ }, reportHorizontalRule: { - borderBottomWidth: 1, borderColor: theme.border, ...spacing.mh5, - ...spacing.mv2, }, assigneeTextStyle: { @@ -3684,27 +3693,26 @@ const styles = (theme) => ({ }, loginButtonRow: { - justifyContent: 'center', width: '100%', + gap: 12, ...flex.flexRow, + ...flex.justifyContentCenter, }, loginButtonRowSmallScreen: { - justifyContent: 'center', width: '100%', - marginBottom: 10, + gap: 12, ...flex.flexRow, + ...flex.justifyContentCenter, + marginBottom: 10, }, - appleButtonContainer: { + desktopSignInButtonContainer: { width: 40, height: 40, - marginRight: 20, }, signInIconButton: { - margin: 10, - marginTop: 0, padding: 2, }, @@ -3712,7 +3720,6 @@ const styles = (theme) => ({ colorScheme: 'light', width: 40, height: 40, - marginLeft: 12, alignItems: 'center', overflow: 'hidden', }, diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js index c101a668666b..6abc8d7e4463 100644 --- a/src/styles/themes/default.js +++ b/src/styles/themes/default.js @@ -82,6 +82,7 @@ const darkTheme = { skeletonLHNOut: colors.darkDefaultButton, QRLogo: colors.green400, starDefaultBG: 'rgb(254, 228, 94)', + loungeAccessOverlay: colors.blue800, }; darkTheme.PAGE_BACKGROUND_COLORS = { diff --git a/src/styles/themes/light.js b/src/styles/themes/light.js index 1a945cb84913..69127e19ae14 100644 --- a/src/styles/themes/light.js +++ b/src/styles/themes/light.js @@ -81,6 +81,7 @@ const lightTheme = { skeletonLHNOut: colors.lightDefaultButtonPressed, QRLogo: colors.green400, starDefaultBG: 'rgb(254, 228, 94)', + loungeAccessOverlay: colors.blue800, }; lightTheme.PAGE_BACKGROUND_COLORS = { diff --git a/src/types/modules/electron.d.ts b/src/types/modules/electron.d.ts new file mode 100644 index 000000000000..09e33f29ba38 --- /dev/null +++ b/src/types/modules/electron.d.ts @@ -0,0 +1,18 @@ +// TODO: Move this type to desktop/contextBridge.js once it is converted to TS +type ContextBridgeApi = { + send: (channel: string, data?: unknown) => void; + sendSync: (channel: string, data?: unknown) => unknown; + invoke: (channel: string, ...args: unknown) => Promise; + on: (channel: string, func: () => void) => void; + removeAllListeners: (channel: string) => void; +}; + +declare global { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions + interface Window { + electron: ContextBridgeApi; + } +} + +// We used the export {} line to mark this file as an external module +export {}; diff --git a/src/types/onyx/ReceiptModal.ts b/src/types/onyx/ReceiptModal.ts deleted file mode 100644 index 0d52f684b4d2..000000000000 --- a/src/types/onyx/ReceiptModal.ts +++ /dev/null @@ -1,7 +0,0 @@ -type ReceiptModal = { - isAttachmentInvalid: boolean; - attachmentInvalidReasonTitle: string; - attachmentInvalidReason: string; -}; - -export default ReceiptModal; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 8660837ba874..88caa683305d 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -81,6 +81,8 @@ type Report = { lastActorAccountID?: number; ownerAccountID?: number; participantAccountIDs?: number[]; + total?: number; + currency?: string; }; export default Report; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 4326920ab51f..de128d85e6b1 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -21,7 +21,7 @@ type Routes = Record; type Transaction = { transactionID: string; amount: number; - category?: string; + category: string; currency: string; reportID: string; comment: Comment; @@ -38,6 +38,7 @@ type Transaction = { state?: ValueOf; }; routes?: Routes; + tag: string; }; export default Transaction; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 8711a0d208ef..069909153096 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -29,7 +29,6 @@ import FrequentlyUsedEmoji from './FrequentlyUsedEmoji'; import ReimbursementAccount from './ReimbursementAccount'; import ReimbursementAccountDraft from './ReimbursementAccountDraft'; import WalletTransfer from './WalletTransfer'; -import ReceiptModal from './ReceiptModal'; import MapboxAccessToken from './MapboxAccessToken'; import {OnyxUpdatesFromServer, OnyxUpdateEvent} from './OnyxUpdatesFromServer'; import Download from './Download'; @@ -79,7 +78,6 @@ export type { ReimbursementAccountDraft, FrequentlyUsedEmoji, WalletTransfer, - ReceiptModal, MapboxAccessToken, Download, PolicyMember, diff --git a/tests/README.md b/tests/README.md index 4ac21499c0f4..dd5b5fc1635f 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,7 +6,7 @@ - Much of the logic in the app is asynchronous in nature. [`react-native-onyx`](https://github.com/expensify/react-native-onyx) relies on [`AsyncStorage`](https://github.com/react-native-async-storage/async-storage) and writes data async before updating subscribers. - [Actions](https://github.com/Expensify/App#actions) do not typically return a `Promise` and therefore can't always be "awaited" before running an assertion. -- To test a result after some asynchronous code has run we can use [`Onyx.connect()`](https://github.com/Expensify/react-native-onyx/blob/2c94a94e51fab20330f7bd5381b72ea6c25553d9/lib/Onyx.js#L217-L231) and the helper method [`waitForPromisesToResolve()`](https://github.com/Expensify/ReactNativeChat/blob/ca2fa88a5789b82463d35eddc3d57f70a7286868/tests/utils/waitForPromisesToResolve.js#L1-L9) which returns a `Promise` and will ensure that all other `Promises` have finished running before resolving. +- To test a result after some asynchronous code has run we can use [`Onyx.connect()`](https://github.com/Expensify/react-native-onyx/blob/2c94a94e51fab20330f7bd5381b72ea6c25553d9/lib/Onyx.js#L217-L231) and the helper method [`waitForBatchedUpdates()`](https://github.com/Expensify/ReactNativeChat/blob/ca2fa88a5789b82463d35eddc3d57f70a7286868/tests/utils/waitForBatchedUpdates.js#L1-L9) which returns a `Promise` and will ensure that all other `Promises` have finished running before resolving. - **Important Note:** When writing any asynchronous Jest test it's very important that your test itself **return a `Promise`**. ## Mocking Network Requests @@ -65,7 +65,7 @@ describe('actions/Report', () => { // When we add a new action to that report Report.addComment(REPORT_ID, 'Hello!'); - return waitForPromisesToResolve() + return waitForBatchedUpdates() .then(() => { const action = reportActions[ACTION_ID]; diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js index afb06cdb6fb3..01c2b4711ce7 100644 --- a/tests/actions/IOUTest.js +++ b/tests/actions/IOUTest.js @@ -2,7 +2,7 @@ import _ from 'underscore'; import Onyx from 'react-native-onyx'; import CONST from '../../src/CONST'; import ONYXKEYS from '../../src/ONYXKEYS'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import * as IOU from '../../src/libs/actions/IOU'; import * as TestHelper from '../utils/TestHelper'; import DateUtils from '../../src/libs/DateUtils'; @@ -10,6 +10,7 @@ import * as NumberUtils from '../../src/libs/NumberUtils'; import * as ReportActions from '../../src/libs/actions/ReportActions'; import * as Report from '../../src/libs/actions/Report'; import OnyxUpdateManager from '../../src/libs/actions/OnyxUpdateManager'; +import waitForNetworkPromises from '../utils/waitForNetworkPromises'; const CARLOS_EMAIL = 'cmartins@expensifail.com'; const CARLOS_ACCOUNT_ID = 1; @@ -30,7 +31,7 @@ describe('actions/IOU', () => { beforeEach(() => { global.fetch = TestHelper.getGlobalFetchMock(); - return Onyx.clear().then(waitForPromisesToResolve); + return Onyx.clear().then(waitForBatchedUpdates); }); describe('requestMoney', () => { @@ -44,7 +45,7 @@ describe('actions/IOU', () => { let transactionID; fetch.pause(); IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment); - return waitForPromisesToResolve() + return waitForBatchedUpdates() .then( () => new Promise((resolve) => { @@ -209,7 +210,7 @@ describe('actions/IOU', () => { ) .then(() => { IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then( () => @@ -309,6 +310,7 @@ describe('actions/IOU', () => { }), ) .then(fetch.resume) + .then(waitForBatchedUpdates) .then( () => new Promise((resolve) => { @@ -400,7 +402,7 @@ describe('actions/IOU', () => { .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${existingTransaction.transactionID}`, existingTransaction)) .then(() => { IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then( () => @@ -491,6 +493,7 @@ describe('actions/IOU', () => { }), ) .then(fetch.resume) + .then(waitForNetworkPromises) .then( () => new Promise((resolve) => { @@ -533,7 +536,7 @@ describe('actions/IOU', () => { fetch.pause(); IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() .then( () => new Promise((resolve) => { @@ -904,7 +907,7 @@ describe('actions/IOU', () => { comment, CONST.CURRENCY.USD, ); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then( () => @@ -1127,6 +1130,7 @@ describe('actions/IOU', () => { }), ) .then(fetch.resume) + .then(waitForNetworkPromises) .then( () => new Promise((resolve) => { @@ -1187,7 +1191,7 @@ describe('actions/IOU', () => { let payIOUAction; let transaction; IOU.requestMoney({}, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment); - return waitForPromisesToResolve() + return waitForBatchedUpdates() .then( () => new Promise((resolve) => { @@ -1267,7 +1271,7 @@ describe('actions/IOU', () => { .then(() => { fetch.pause(); IOU.payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then( () => diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 978186fcf9c4..62109089665c 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -6,7 +6,7 @@ import {beforeEach, beforeAll, afterEach, describe, it, expect} from '@jest/glob import ONYXKEYS from '../../src/ONYXKEYS'; import CONST from '../../src/CONST'; import * as Report from '../../src/libs/actions/Report'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import PusherHelper from '../utils/PusherHelper'; import * as TestHelper from '../utils/TestHelper'; import Log from '../../src/libs/Log'; @@ -14,7 +14,10 @@ import * as PersistedRequests from '../../src/libs/actions/PersistedRequests'; import * as User from '../../src/libs/actions/User'; import * as ReportUtils from '../../src/libs/ReportUtils'; import DateUtils from '../../src/libs/DateUtils'; +import * as SequentialQueue from '../../src/libs/Network/SequentialQueue'; import OnyxUpdateManager from '../../src/libs/actions/OnyxUpdateManager'; +import waitForNetworkPromises from '../utils/waitForNetworkPromises'; +import getIsUsingFakeTimers from '../utils/getIsUsingFakeTimers'; jest.mock('../../src/libs/actions/Report', () => { const originalModule = jest.requireActual('../../src/libs/actions/Report'); @@ -35,7 +38,15 @@ describe('actions/Report', () => { }); }); - beforeEach(() => Onyx.clear().then(waitForPromisesToResolve)); + beforeEach(() => { + const promise = Onyx.clear().then(jest.useRealTimers); + if (getIsUsingFakeTimers()) { + // flushing pending timers + // Onyx.clear() promise is resolved in batch which happends after the current microtasks cycle + setImmediate(jest.runOnlyPendingTimers); + } + return promise; + }); afterEach(PusherHelper.teardown); @@ -66,14 +77,14 @@ describe('actions/Report', () => { return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN) .then(() => { User.subscribeToUserEvents(); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID)) .then(() => { // This is a fire and forget response, but once it completes we should be able to verify that we // have an "optimistic" report action in Onyx. Report.addComment(REPORT_ID, 'Testing a comment'); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { const resultAction = _.first(_.values(reportActions)); @@ -109,7 +120,7 @@ describe('actions/Report', () => { // Once a reportComment event is emitted to the Pusher channel we should see the comment get processed // by the Pusher callback and added to the storage so we must wait for promises to resolve again and // then verify the data is in Onyx. - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Verify there is only one action and our optimistic comment has been removed @@ -137,7 +148,7 @@ describe('actions/Report', () => { return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN) .then(() => { Report.togglePinnedState(REPORT_ID, false); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Test that Onyx immediately updated the report pin state. @@ -169,7 +180,7 @@ describe('actions/Report', () => { expect(PersistedRequests.getAll().length).toBe(1); // When we wait for the queue to run - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // THEN only ONE call to AddComment will happen @@ -180,6 +191,8 @@ describe('actions/Report', () => { }); it('should be updated correctly when new comments are added, deleted or marked as unread', () => { + jest.useFakeTimers(); + global.fetch = TestHelper.getGlobalFetchMock(); const REPORT_ID = '1'; let report; let reportActionCreatedDate; @@ -198,12 +211,13 @@ describe('actions/Report', () => { const USER_1_LOGIN = 'user@test.com'; const USER_1_ACCOUNT_ID = 1; const USER_2_ACCOUNT_ID = 2; - return Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, {reportName: 'Test', reportID: REPORT_ID}) + const setPromise = Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, {reportName: 'Test', reportID: REPORT_ID}) .then(() => TestHelper.signInWithTestUser(USER_1_ACCOUNT_ID, USER_1_LOGIN)) + .then(waitForNetworkPromises) .then(() => { // Given a test user that is subscribed to Pusher events User.subscribeToUserEvents(); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => TestHelper.setPersonalDetails(USER_1_LOGIN, USER_1_ACCOUNT_ID)) .then(() => { @@ -241,7 +255,7 @@ describe('actions/Report', () => { }, }, ]); - return waitForPromisesToResolve(); + return waitForNetworkPromises(); }) .then(() => { // Then the report will be unread @@ -255,7 +269,8 @@ describe('actions/Report', () => { currentTime = DateUtils.getDBTime(); Report.openReport(REPORT_ID); Report.readNewestAction(REPORT_ID); - return waitForPromisesToResolve(); + waitForBatchedUpdates(); + return waitForBatchedUpdates(); }) .then(() => { // The report will be read @@ -268,7 +283,7 @@ describe('actions/Report', () => { // When the user manually marks a message as "unread" jest.advanceTimersByTime(10); Report.markCommentAsUnread(REPORT_ID, reportActionCreatedDate); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Then the report will be unread and show the green dot for unread mentions in LHN @@ -280,7 +295,7 @@ describe('actions/Report', () => { jest.advanceTimersByTime(10); currentTime = DateUtils.getDBTime(); Report.addComment(REPORT_ID, 'Current User Comment 1'); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // The report will be read, the green dot for unread mentions will go away, and the lastReadTime updated @@ -293,7 +308,7 @@ describe('actions/Report', () => { jest.advanceTimersByTime(10); currentTime = DateUtils.getDBTime(); Report.addComment(REPORT_ID, 'Current User Comment 2'); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // The report will be read and the lastReadTime updated @@ -305,7 +320,7 @@ describe('actions/Report', () => { jest.advanceTimersByTime(10); currentTime = DateUtils.getDBTime(); Report.addComment(REPORT_ID, 'Current User Comment 3'); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // The report will be read and the lastReadTime updated @@ -369,12 +384,12 @@ describe('actions/Report', () => { optimisticReportActions, ]); - return waitForPromisesToResolve(); + return waitForNetworkPromises(); }) .then(() => { // If the user deletes a comment that is before the last read Report.deleteReportComment(REPORT_ID, {...reportActions[200]}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Then no change will occur @@ -383,7 +398,7 @@ describe('actions/Report', () => { // When the user manually marks a message as "unread" Report.markCommentAsUnread(REPORT_ID, reportActionCreatedDate); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Then we should expect the report to be to be unread @@ -392,12 +407,14 @@ describe('actions/Report', () => { // If the user deletes the last comment after the lastReadTime the lastMessageText will reflect the new last comment Report.deleteReportComment(REPORT_ID, {...reportActions[400]}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { expect(ReportUtils.isUnread(report)).toBe(false); expect(report.lastMessageText).toBe('Current User Comment 2'); }); + waitForBatchedUpdates(); // flushing onyx.set as it will be batched + return setPromise; }); it('Should properly update comment with links', () => { @@ -406,6 +423,8 @@ describe('actions/Report', () => { * already in the comment and the user deleted it on purpose. */ + global.fetch = TestHelper.getGlobalFetchMock(); + // User edits comment to add link // We should generate link let originalCommentHTML = 'Original Comment'; @@ -488,9 +507,10 @@ describe('actions/Report', () => { // Setup user and pusher listeners return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID) + .then(waitForBatchedUpdates) .then(() => { User.subscribeToUserEvents(); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Simulate a Pusher Onyx update with a report action with shouldNotify @@ -504,7 +524,7 @@ describe('actions/Report', () => { shouldNotify: true, }, ]); - return waitForPromisesToResolve(); + return SequentialQueue.getCurrentRequest().then(waitForBatchedUpdates); }) .then(() => { // Ensure we show a notification for this new report action @@ -546,14 +566,14 @@ describe('actions/Report', () => { return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN) .then(() => { User.subscribeToUserEvents(); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID)) .then(() => { // This is a fire and forget response, but once it completes we should be able to verify that we // have an "optimistic" report action in Onyx. Report.addComment(REPORT_ID, 'Testing a comment'); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { reportAction = _.first(_.values(reportActions)); @@ -561,7 +581,7 @@ describe('actions/Report', () => { // Add a reaction to the comment Report.toggleEmojiReaction(REPORT_ID, reportAction, EMOJI); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { reportAction = _.first(_.values(reportActions)); @@ -579,7 +599,7 @@ describe('actions/Report', () => { // Now we remove the reaction Report.toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionReaction); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Expect the reaction to have null where the users reaction used to be @@ -592,13 +612,13 @@ describe('actions/Report', () => { // Add the same reaction to the same report action with a different skintone Report.toggleEmojiReaction(REPORT_ID, reportAction, EMOJI); - return waitForPromisesToResolve() + return waitForBatchedUpdates() .then(() => { reportAction = _.first(_.values(reportActions)); const reportActionReaction = reportActionsReactions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`]; Report.toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionReaction, EMOJI_SKIN_TONE); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { reportAction = _.first(_.values(reportActions)); @@ -621,7 +641,7 @@ describe('actions/Report', () => { // Now we remove the reaction, and expect that both variations are removed Report.toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionReaction); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Expect the reaction to have null where the users reaction used to be @@ -664,21 +684,21 @@ describe('actions/Report', () => { return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN) .then(() => { User.subscribeToUserEvents(); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID)) .then(() => { // This is a fire and forget response, but once it completes we should be able to verify that we // have an "optimistic" report action in Onyx. Report.addComment(REPORT_ID, 'Testing a comment'); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { resultAction = _.first(_.values(reportActions)); // Add a reaction to the comment Report.toggleEmojiReaction(REPORT_ID, resultAction, EMOJI, {}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { resultAction = _.first(_.values(reportActions)); @@ -688,7 +708,7 @@ describe('actions/Report', () => { // should get removed instead of added again. const reportActionReaction = reportActionsReactions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${resultAction.reportActionID}`]; Report.toggleEmojiReaction(REPORT_ID, resultAction, EMOJI, reportActionReaction, 2); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Expect the reaction to have null where the users reaction used to be diff --git a/tests/actions/SessionTest.js b/tests/actions/SessionTest.js index 59a7441679ea..4ecb2a33b763 100644 --- a/tests/actions/SessionTest.js +++ b/tests/actions/SessionTest.js @@ -1,7 +1,7 @@ import Onyx from 'react-native-onyx'; import {beforeEach, jest, test} from '@jest/globals'; import HttpUtils from '../../src/libs/HttpUtils'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import ONYXKEYS from '../../src/ONYXKEYS'; import * as TestHelper from '../utils/TestHelper'; import CONST from '../../src/CONST'; @@ -26,7 +26,7 @@ Onyx.init({ }); OnyxUpdateManager(); -beforeEach(() => Onyx.clear().then(waitForPromisesToResolve)); +beforeEach(() => Onyx.clear().then(waitForBatchedUpdates)); describe('Session', () => { test('Authenticate is called with saved credentials when a session expires', () => { @@ -50,6 +50,7 @@ describe('Session', () => { // When we sign in with the test user return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN, 'Password1', TEST_INITIAL_AUTH_TOKEN) + .then(waitForBatchedUpdates) .then(() => { // Then our re-authentication credentials should be generated and our session data // have the correct information + initial authToken. @@ -87,7 +88,7 @@ describe('Session', () => { // When we attempt to fetch the initial app data via the API App.confirmReadyToOpenApp(); App.openApp(); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Then it should fail and reauthenticate the user adding the new authToken to the session @@ -96,7 +97,10 @@ describe('Session', () => { }); }); - test('Push notifications are subscribed after signing in', () => TestHelper.signInWithTestUser().then(() => expect(PushNotification.register).toBeCalled())); + test('Push notifications are subscribed after signing in', () => + TestHelper.signInWithTestUser() + .then(waitForBatchedUpdates) + .then(() => expect(PushNotification.register).toBeCalled())); test('Push notifications are unsubscribed after signing out', () => TestHelper.signInWithTestUser() diff --git a/tests/perf-test/SidebarLinks.perf-test.js b/tests/perf-test/SidebarLinks.perf-test.js index 4600f42bfd1d..87d2648f78dd 100644 --- a/tests/perf-test/SidebarLinks.perf-test.js +++ b/tests/perf-test/SidebarLinks.perf-test.js @@ -3,7 +3,7 @@ import Onyx from 'react-native-onyx'; import _ from 'underscore'; import * as LHNTestUtils from '../utils/LHNTestUtils'; import CONST from '../../src/CONST'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; jest.mock('../../src/libs/Permissions'); jest.mock('../../src/components/Icon/Expensicons'); @@ -45,7 +45,7 @@ test('simple Sidebar render with hundred of reports', () => { }); const mockOnyxReports = _.assign({}, ...mockReports); - return waitForPromisesToResolve() + return waitForBatchedUpdates() .then(() => Onyx.multiSet({ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js index 1666ffb87400..6a64dda85b37 100644 --- a/tests/ui/UnreadIndicatorsTest.js +++ b/tests/ui/UnreadIndicatorsTest.js @@ -7,8 +7,8 @@ import moment from 'moment'; import App from '../../src/App'; import CONST from '../../src/CONST'; import ONYXKEYS from '../../src/ONYXKEYS'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; -import waitForPromisesToResolveWithAct from '../utils/waitForPromisesToResolveWithAct'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; import * as TestHelper from '../utils/TestHelper'; import appSetup from '../../src/setup'; import fontWeightBold from '../../src/styles/fontWeight/bold'; @@ -87,7 +87,7 @@ function navigateToSidebar() { const hintText = Localize.translateLocal('accessibilityHints.navigateToChatsList'); const reportHeaderBackButton = screen.queryByAccessibilityHint(hintText); fireEvent(reportHeaderBackButton, 'press'); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); } /** @@ -98,7 +98,7 @@ function navigateToSidebarOption(index) { const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); const optionRows = screen.queryAllByAccessibilityHint(hintText); fireEvent(optionRows[index], 'press'); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); } /** @@ -129,7 +129,7 @@ let reportAction9CreatedDate; function signInAndGetAppWithUnreadChat() { // Render the App and sign in as a test user. render(); - return waitForPromisesToResolveWithAct() + return waitForBatchedUpdatesWithAct() .then(() => { const hintText = Localize.translateLocal('loginForm.loginForm'); const loginForm = screen.queryAllByLabelText(hintText); @@ -139,7 +139,7 @@ function signInAndGetAppWithUnreadChat() { }) .then(() => { User.subscribeToUserEvents(); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { const MOMENT_TEN_MINUTES_AGO = moment().subtract(10, 'minutes'); @@ -192,7 +192,7 @@ function signInAndGetAppWithUnreadChat() { // We manually setting the sidebar as loaded since the onLayout event does not fire in tests AppActions.setSidebarLoaded(true); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }); } @@ -342,7 +342,7 @@ describe('Unread Indicators', () => { ], }, ]); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Verify notification was created @@ -388,7 +388,7 @@ describe('Unread Indicators', () => { // It's difficult to trigger marking a report comment as unread since we would have to mock the long press event and then // another press on the context menu item so we will do it via the action directly and then test if the UI has updated properly Report.markCommentAsUnread(REPORT_ID, reportAction3CreatedDate); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Verify the indicator appears above the last action @@ -454,7 +454,7 @@ describe('Unread Indicators', () => { // Leave a comment as the current user and verify the indicator is removed Report.addComment(REPORT_ID, 'Current User Comment 1'); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); @@ -487,7 +487,7 @@ describe('Unread Indicators', () => { // Mark a previous comment as unread and verify the unread action indicator returns Report.markCommentAsUnread(REPORT_ID, reportAction9CreatedDate); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator'); @@ -517,7 +517,7 @@ describe('Unread Indicators', () => { .then(() => { // Leave a comment as the current user Report.addComment(REPORT_ID, 'Current User Comment 1'); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Simulate the response from the server so that the comment can be deleted in this test @@ -528,7 +528,7 @@ describe('Unread Indicators', () => { lastActorAccountID: lastReportAction.actorAccountID, reportID: REPORT_ID, }); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Verify the chat preview text matches the last comment from the current user @@ -538,7 +538,7 @@ describe('Unread Indicators', () => { expect(alternateText[0].props.children).toBe('Current User Comment 1'); Report.deleteReportComment(REPORT_ID, lastReportAction); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { const hintText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview'); diff --git a/tests/unit/APITest.js b/tests/unit/APITest.js index eeeddc70fc47..395f1438b666 100644 --- a/tests/unit/APITest.js +++ b/tests/unit/APITest.js @@ -3,7 +3,7 @@ import _ from 'underscore'; import * as TestHelper from '../utils/TestHelper'; import HttpUtils from '../../src/libs/HttpUtils'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import ONYXKEYS from '../../src/ONYXKEYS'; import CONST from '../../src/CONST'; import * as NetworkStore from '../../src/libs/Network/NetworkStore'; @@ -12,9 +12,10 @@ import * as MainQueue from '../../src/libs/Network/MainQueue'; import * as API from '../../src/libs/API'; import * as SequentialQueue from '../../src/libs/Network/SequentialQueue'; import * as Request from '../../src/libs/Request'; +import * as RequestThrottle from '../../src/libs/RequestThrottle'; +import waitForNetworkPromises from '../utils/waitForNetworkPromises'; jest.mock('../../src/libs/Log'); -jest.useFakeTimers(); Onyx.init({ keys: ONYXKEYS, @@ -31,10 +32,9 @@ beforeEach(() => { NetworkStore.checkRequiredData(); // Wait for any Log command to finish and Onyx to fully clear - jest.advanceTimersByTime(CONST.NETWORK.PROCESS_REQUEST_DELAY_MS); - return waitForPromisesToResolve() + return waitForBatchedUpdates() .then(() => Onyx.clear()) - .then(waitForPromisesToResolve); + .then(waitForBatchedUpdates); }); afterEach(() => { @@ -57,7 +57,7 @@ describe('APITests', () => { API.write('mock command', {param1: 'value1'}); API.read('mock command', {param2: 'value2'}); API.write('mock command', {param3: 'value3'}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Then `xhr` should only be called for the read (where it would not succeed in real life) and write requests should be persisted to storage @@ -70,7 +70,7 @@ describe('APITests', () => { ]); PersistedRequests.clear(); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { expect(PersistedRequests.getAll()).toEqual([]); @@ -92,7 +92,7 @@ describe('APITests', () => { // When API Write commands are made API.write('mock command', {param1: 'value1'}); API.write('mock command', {param2: 'value2'}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { const persisted = PersistedRequests.getAll(); @@ -101,7 +101,7 @@ describe('APITests', () => { // When we resume connectivity .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) - .then(waitForPromisesToResolve) + .then(waitForBatchedUpdates) .then(() => { expect(NetworkStore.isOffline()).toBe(false); expect(SequentialQueue.isRunning()).toBe(false); @@ -141,40 +141,39 @@ describe('APITests', () => { // When API Write commands are made API.write('mock command', {param1: 'value1'}); API.write('mock command', {param2: 'value2'}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) // When we resume connectivity .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) - .then(waitForPromisesToResolve) + .then(waitForBatchedUpdates) .then(() => { // Then requests should remain persisted until the xhr call is resolved expect(_.size(PersistedRequests.getAll())).toEqual(2); xhrCalls[0].resolve({jsonCode: CONST.JSON_CODE.SUCCESS}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) - .then(waitForPromisesToResolve) + .then(waitForBatchedUpdates) .then(() => { expect(_.size(PersistedRequests.getAll())).toEqual(1); expect(PersistedRequests.getAll()).toEqual([expect.objectContaining({command: 'mock command', data: expect.objectContaining({param2: 'value2'})})]); // When a request fails it should be retried xhrCalls[1].reject(new Error(CONST.ERROR.FAILED_TO_FETCH)); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { expect(_.size(PersistedRequests.getAll())).toEqual(1); expect(PersistedRequests.getAll()).toEqual([expect.objectContaining({command: 'mock command', data: expect.objectContaining({param2: 'value2'})})]); // We need to advance past the request throttle back off timer because the request won't be retried until then - jest.advanceTimersByTime(CONST.NETWORK.MAX_RANDOM_RETRY_WAIT_TIME_MS); - return waitForPromisesToResolve(); + return new Promise((resolve) => setTimeout(resolve, CONST.NETWORK.MAX_RANDOM_RETRY_WAIT_TIME_MS)).then(waitForBatchedUpdates); }) .then(() => { // Finally, after it succeeds the queue should be empty xhrCalls[2].resolve({jsonCode: CONST.JSON_CODE.SUCCESS}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { expect(_.size(PersistedRequests.getAll())).toEqual(0); @@ -204,12 +203,12 @@ describe('APITests', () => { .then(() => { // When API Write commands are made API.write('mock command', {param1: 'value1'}); - return waitForPromisesToResolve(); + return waitForNetworkPromises(); }) // When we resume connectivity .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) - .then(waitForPromisesToResolve) + .then(waitForBatchedUpdates) .then(() => { // Then there has only been one request so far expect(global.fetch).toHaveBeenCalledTimes(1); @@ -219,8 +218,7 @@ describe('APITests', () => { expect(PersistedRequests.getAll()).toEqual([expect.objectContaining({command: 'mock command', data: expect.objectContaining({param1: 'value1'})})]); // We let the SequentialQueue process again after its wait time - jest.runOnlyPendingTimers(); - return waitForPromisesToResolve(); + return new Promise((resolve) => setTimeout(resolve, RequestThrottle.getLastRequestWaitTime())); }) .then(() => { // Then we have retried the failing request @@ -231,8 +229,7 @@ describe('APITests', () => { expect(PersistedRequests.getAll()).toEqual([expect.objectContaining({command: 'mock command', data: expect.objectContaining({param1: 'value1'})})]); // We let the SequentialQueue process again after its wait time - jest.runOnlyPendingTimers(); - return waitForPromisesToResolve(); + return new Promise((resolve) => setTimeout(resolve, RequestThrottle.getLastRequestWaitTime())).then(waitForBatchedUpdates); }) .then(() => { // Then the request is retried again @@ -284,16 +281,16 @@ describe('APITests', () => { // Given we have a request made while we're offline and we have credentials available to reauthenticate Onyx.merge(ONYXKEYS.CREDENTIALS, {autoGeneratedLogin: 'test', autoGeneratedPassword: 'passwd'}); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: true})) .then(() => { API.write('Mock', {param1: 'value1'}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) // When we resume connectivity .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) - .then(waitForPromisesToResolve) + .then(waitForBatchedUpdates) .then(() => { const nonLogCalls = _.filter(xhr.mock.calls, ([commandName]) => commandName !== 'Log'); @@ -328,10 +325,10 @@ describe('APITests', () => { API.write('MockCommand', {content: 'value5'}); API.write('MockCommand', {content: 'value6'}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) - .then(waitForPromisesToResolve) + .then(waitForBatchedUpdates) .then(() => { // Then expect all 7 calls to have been made and for the Writes to be made in the order that we made them // The read command would have been made first (and would have failed in real-life) @@ -363,10 +360,10 @@ describe('APITests', () => { API.write('MockCommand', {content: 'value4'}); API.write('MockCommand', {content: 'value5'}); API.write('MockCommand', {content: 'value6'}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) - .then(waitForPromisesToResolve) + .then(waitForBatchedUpdates) .then(() => { // Then expect only 8 calls to have been made total and for them to be made in the order that we made them despite requiring reauthentication expect(xhr.mock.calls.length).toBe(8); @@ -401,10 +398,11 @@ describe('APITests', () => { .then(() => { // When we queue both non-persistable and persistable commands that will trigger reauthentication and go offline at the same time API.makeRequestWithSideEffects('AuthenticatePusher', {content: 'value1'}); + Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}); expect(NetworkStore.isOffline()).toBe(false); expect(NetworkStore.isAuthenticating()).toBe(false); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { API.write('MockCommand'); @@ -416,13 +414,15 @@ describe('APITests', () => { // We should only have a single call at this point as the main queue is stopped since we've gone offline expect(xhr.mock.calls.length).toBe(1); + waitForBatchedUpdates(); + // Come back from offline to trigger the sequential queue flush - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}); + Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}); }) .then(() => { // When we wait for the sequential queue to finish expect(SequentialQueue.isRunning()).toBe(true); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Then we should expect to see that... @@ -482,7 +482,7 @@ describe('APITests', () => { // When we go online and wait for promises to resolve return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}); }) - .then(waitForPromisesToResolve) + .then(waitForBatchedUpdates) .then(() => { expect(processWithMiddleware).toHaveBeenCalled(); @@ -495,7 +495,7 @@ describe('APITests', () => { [ONYXKEYS.SESSION]: {authToken: 'oldToken'}, }); }) - .then(waitForPromisesToResolve) + .then(waitForBatchedUpdates) .then(() => { // Then we should expect XHR to run expect(xhr).toHaveBeenCalled(); @@ -518,7 +518,7 @@ describe('APITests', () => { expect(PersistedRequests.getAll().length).toBe(2); // WHEN we wait for the queue to run and finish processing - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // THEN the queue should be stopped and there should be no more requests to run @@ -535,8 +535,7 @@ describe('APITests', () => { expect(secondRequestCommandName).toBe('MockCommandThree'); // WHEN we advance the main queue timer and wait for promises - jest.advanceTimersByTime(CONST.NETWORK.PROCESS_REQUEST_DELAY_MS); - return waitForPromisesToResolve(); + return new Promise((resolve) => setTimeout(resolve, CONST.NETWORK.PROCESS_REQUEST_DELAY_MS)); }) .then(() => { // THEN we should see that our third (non-persistable) request has run last diff --git a/tests/unit/CurrencyUtilsTest.js b/tests/unit/CurrencyUtilsTest.js index 5058c56bfa00..ba61775f30da 100644 --- a/tests/unit/CurrencyUtilsTest.js +++ b/tests/unit/CurrencyUtilsTest.js @@ -2,7 +2,7 @@ import _ from 'underscore'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../src/ONYXKEYS'; import CONST from '../../src/CONST'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import * as CurrencyUtils from '../../src/libs/CurrencyUtils'; import LocaleListener from '../../src/libs/Localize/LocaleListener'; @@ -30,7 +30,7 @@ describe('CurrencyUtils', () => { }, }); LocaleListener.connect(); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }); afterEach(() => Onyx.clear()); diff --git a/tests/unit/DateUtilsTest.js b/tests/unit/DateUtilsTest.js index d17c1c052929..d8ea5e3b147a 100644 --- a/tests/unit/DateUtilsTest.js +++ b/tests/unit/DateUtilsTest.js @@ -4,7 +4,7 @@ import {addMinutes, subHours, subMinutes, subSeconds, format, setMinutes, setHou import CONST from '../../src/CONST'; import DateUtils from '../../src/libs/DateUtils'; import ONYXKEYS from '../../src/ONYXKEYS'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; const LOCALE = CONST.LOCALES.EN; @@ -17,7 +17,7 @@ describe('DateUtils', () => { [ONYXKEYS.PERSONAL_DETAILS_LIST]: {999: {timezone: {selected: 'UTC'}}}, }, }); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }); afterEach(() => { diff --git a/tests/unit/EmojiTest.js b/tests/unit/EmojiTest.js index 0e9b7a7c8c0f..2cc38648f0d9 100644 --- a/tests/unit/EmojiTest.js +++ b/tests/unit/EmojiTest.js @@ -6,7 +6,7 @@ import Emoji from '../../assets/emojis'; import * as EmojiUtils from '../../src/libs/EmojiUtils'; import ONYXKEYS from '../../src/ONYXKEYS'; import * as User from '../../src/libs/actions/User'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import * as TestHelper from '../utils/TestHelper'; import CONST from '../../src/CONST'; @@ -170,8 +170,7 @@ describe('EmojiTest', () => { beforeEach(() => { spy.mockClear(); - Onyx.clear(); - return waitForPromisesToResolve(); + return Onyx.clear(); }); it('should put a less frequent and recent used emoji behind', () => { @@ -205,7 +204,7 @@ describe('EmojiTest', () => { ]; Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); - return waitForPromisesToResolve().then(() => { + return waitForBatchedUpdates().then(() => { // When add a new emoji const currentTime = moment().unix(); const smileEmoji = {code: '😄', name: 'smile'}; @@ -253,7 +252,7 @@ describe('EmojiTest', () => { ]; Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); - return waitForPromisesToResolve().then(() => { + return waitForBatchedUpdates().then(() => { // When add an emoji that exists in the list const currentTime = moment().unix(); const newEmoji = [smileEmoji]; @@ -295,7 +294,7 @@ describe('EmojiTest', () => { ]; Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); - return waitForPromisesToResolve().then(() => { + return waitForBatchedUpdates().then(() => { // When add multiple emojis that either exist or not exist in the list const currentTime = moment().unix(); const newEmoji = [smileEmoji, zzzEmoji, impEmoji]; @@ -466,7 +465,7 @@ describe('EmojiTest', () => { expect(frequentlyEmojisList.length).toBe(CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW); Onyx.merge(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyEmojisList); - return waitForPromisesToResolve().then(() => { + return waitForBatchedUpdates().then(() => { // When add new emojis const currentTime = moment().unix(); const newEmoji = [bookEmoji, smileEmoji, zzzEmoji, impEmoji, smileEmoji]; diff --git a/tests/unit/FileUtilsTest.js b/tests/unit/FileUtilsTest.js index 34dc0dfcf129..a5f4f34fe913 100644 --- a/tests/unit/FileUtilsTest.js +++ b/tests/unit/FileUtilsTest.js @@ -2,6 +2,8 @@ import CONST from '../../src/CONST'; import DateUtils from '../../src/libs/DateUtils'; import * as FileUtils from '../../src/libs/fileDownload/FileUtils'; +jest.useFakeTimers(); + describe('FileUtils', () => { describe('splitExtensionFromFileName', () => { it('should return correct file name and extension', () => { diff --git a/tests/unit/IOUUtilsTest.js b/tests/unit/IOUUtilsTest.js index 22790ebe721f..9ea30638af87 100644 --- a/tests/unit/IOUUtilsTest.js +++ b/tests/unit/IOUUtilsTest.js @@ -2,7 +2,7 @@ import Onyx from 'react-native-onyx'; import * as IOUUtils from '../../src/libs/IOUUtils'; import * as ReportUtils from '../../src/libs/ReportUtils'; import ONYXKEYS from '../../src/ONYXKEYS'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import currencyList from './currencyList.json'; import * as TransactionUtils from '../../src/libs/TransactionUtils'; @@ -13,7 +13,7 @@ function initCurrencyList() { [ONYXKEYS.CURRENCY_LIST]: currencyList, }, }); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); } describe('IOUUtils', () => { diff --git a/tests/unit/LocalePhoneNumberTest.js b/tests/unit/LocalePhoneNumberTest.js index e265cb7a35e6..1435e0819fa4 100644 --- a/tests/unit/LocalePhoneNumberTest.js +++ b/tests/unit/LocalePhoneNumberTest.js @@ -1,7 +1,7 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../src/ONYXKEYS'; import * as LocalePhoneNumber from '../../src/libs/LocalePhoneNumber'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; const ES_NUMBER = '+34702474537'; const US_NUMBER = '+18332403627'; @@ -20,7 +20,7 @@ describe('LocalePhoneNumber utils', () => { Onyx.multiSet({ [ONYXKEYS.SESSION]: {email: 'current@user.com'}, [ONYXKEYS.COUNTRY_CODE]: 1, - }).then(waitForPromisesToResolve), + }).then(waitForBatchedUpdates), ); afterEach(() => Onyx.clear()); diff --git a/tests/unit/LocalizeTests.js b/tests/unit/LocalizeTests.js index eebc3d13ab7b..921cf158a47c 100644 --- a/tests/unit/LocalizeTests.js +++ b/tests/unit/LocalizeTests.js @@ -1,5 +1,5 @@ import Onyx from 'react-native-onyx'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import CONST from '../../src/CONST'; import ONYXKEYS from '../../src/ONYXKEYS'; import * as Localize from '../../src/libs/Localize'; @@ -10,7 +10,7 @@ describe('localize', () => { keys: {NVP_PREFERRED_LOCALE: ONYXKEYS.NVP_PREFERRED_LOCALE}, initialKeyStates: {[ONYXKEYS.NVP_PREFERRED_LOCALE]: CONST.LOCALES.DEFAULT}, }); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }); afterEach(() => Onyx.clear()); diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index 0171ee640226..f209c6e4ed38 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -1,12 +1,11 @@ import Onyx from 'react-native-onyx'; import _ from 'underscore'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import CONST from '../../src/CONST'; import Log from '../../src/libs/Log'; import getPlatform from '../../src/libs/getPlatform'; import AddLastVisibleActionCreated from '../../src/libs/migrations/AddLastVisibleActionCreated'; import MoveToIndexedDB from '../../src/libs/migrations/MoveToIndexedDB'; -import KeyReportActionsByReportActionID from '../../src/libs/migrations/KeyReportActionsByReportActionID'; import PersonalDetailsByAccountID from '../../src/libs/migrations/PersonalDetailsByAccountID'; import CheckForPreviousReportActionID from '../../src/libs/migrations/CheckForPreviousReportActionID'; import ONYXKEYS from '../../src/ONYXKEYS'; @@ -20,13 +19,13 @@ describe('Migrations', () => { Onyx.init({keys: ONYXKEYS}); LogSpy = jest.spyOn(Log, 'info'); Log.serverLoggingCallback = () => {}; - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }); beforeEach(() => { jest.clearAllMocks(); Onyx.clear(); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }); describe('MoveToIndexedDb', () => { @@ -150,112 +149,6 @@ describe('Migrations', () => { })); }); - describe('KeyReportActionsByReportActionID', () => { - // Warning: this test has to come before the others in this suite because Onyx.clear leaves traces and keys with null values aren't cleared out between tests - it("Should work even if there's no reportAction data in Onyx", () => - KeyReportActionsByReportActionID().then(() => - expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because there were no reportActions'), - )); - - it("Should work even if there's zombie reportAction data in Onyx", () => - Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: null, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: null, - }) - .then(KeyReportActionsByReportActionID) - .then(() => { - expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because there are no actions to migrate'); - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - waitForCollectionCallback: true, - callback: (allReportActions) => { - Onyx.disconnect(connectionID); - _.each(allReportActions, (reportActionsForReport) => expect(reportActionsForReport).toBeNull()); - }, - }); - })); - - it('Should migrate reportActions to be keyed by reportActionID instead of sequenceNumber', () => - Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { - 1: { - reportActionID: '1000', - sequenceNumber: 1, - }, - 2: { - reportActionID: '2000', - sequenceNumber: 2, - }, - }, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: { - 1: { - reportActionID: '3000', - sequenceNumber: 1, - }, - 2: { - reportActionID: '4000', - sequenceNumber: 2, - }, - }, - }) - .then(KeyReportActionsByReportActionID) - .then(() => { - expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Re-keying reportActions by reportActionID for 2 reports'); - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - waitForCollectionCallback: true, - callback: (allReportActions) => { - Onyx.disconnect(connectionID); - expect(_.keys(allReportActions).length).toBe(2); - _.each(allReportActions, (reportActionsForReport) => { - _.each(reportActionsForReport, (reportAction, key) => { - expect(key).toBe(reportAction.reportActionID); - }); - }); - }, - }); - })); - - it('Should return early if the migration has already happened', () => - Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { - 1000: { - reportActionID: '1000', - sequenceNumber: 1, - }, - 2000: { - reportActionID: '2000', - sequenceNumber: 2, - }, - }, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: { - 3000: { - reportActionID: '3000', - }, - 4000: { - reportActionID: '4000', - }, - }, - }) - .then(KeyReportActionsByReportActionID) - .then(() => { - expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because we already migrated it'); - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - waitForCollectionCallback: true, - callback: (allReportActions) => { - Onyx.disconnect(connectionID); - expect(_.keys(allReportActions).length).toBe(2); - _.each(allReportActions, (reportActionsForReport) => { - _.each(reportActionsForReport, (reportAction, key) => { - expect(key).toBe(reportAction.reportActionID); - }); - }); - }, - }); - })); - }); - describe('PersonalDetailsByAccountID', () => { const DEPRECATED_ONYX_KEYS = { // Deprecated personal details object which was keyed by login instead of accountID. diff --git a/tests/unit/NetworkTest.js b/tests/unit/NetworkTest.js index 7d8c4f23197c..bd45ae3c2187 100644 --- a/tests/unit/NetworkTest.js +++ b/tests/unit/NetworkTest.js @@ -3,7 +3,7 @@ import Onyx from 'react-native-onyx'; import * as TestHelper from '../utils/TestHelper'; import HttpUtils from '../../src/libs/HttpUtils'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import ONYXKEYS from '../../src/ONYXKEYS'; import CONST from '../../src/CONST'; import * as Network from '../../src/libs/Network'; @@ -17,7 +17,6 @@ import NetworkConnection from '../../src/libs/NetworkConnection'; import OnyxUpdateManager from '../../src/libs/actions/OnyxUpdateManager'; jest.mock('../../src/libs/Log'); -jest.useFakeTimers(); Onyx.init({ keys: ONYXKEYS, @@ -31,14 +30,13 @@ beforeEach(() => { HttpUtils.xhr = originalXHR; MainQueue.clear(); HttpUtils.cancelPendingRequests(); - PersistedRequests.clear(); NetworkStore.checkRequiredData(); // Wait for any Log command to finish and Onyx to fully clear - jest.advanceTimersByTime(CONST.NETWORK.PROCESS_REQUEST_DELAY_MS); - return waitForPromisesToResolve() + return waitForBatchedUpdates() + .then(() => PersistedRequests.clear()) .then(() => Onyx.clear()) - .then(waitForPromisesToResolve); + .then(waitForBatchedUpdates); }); afterEach(() => { @@ -106,14 +104,14 @@ describe('NetworkTests', () => { // This should first trigger re-authentication and then a Failed to fetch App.confirmReadyToOpenApp(); App.openApp(); - return waitForPromisesToResolve() + return waitForBatchedUpdates() .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) .then(() => { expect(isOffline).toBe(false); // Advance the network request queue by 1 second so that it can realize it's back online jest.advanceTimersByTime(CONST.NETWORK.PROCESS_REQUEST_DELAY_MS); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Then we will eventually have 3 calls to chatList and 2 calls to Authenticate @@ -174,7 +172,7 @@ describe('NetworkTests', () => { App.openApp(); App.openApp(); App.openApp(); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // We should expect to see the three calls to OpenApp, but only one call to Authenticate. @@ -214,7 +212,7 @@ describe('NetworkTests', () => { // Once credentials are set and we wait for promises to resolve Onyx.merge(ONYXKEYS.CREDENTIALS, {login: 'test-login'}); Onyx.merge(ONYXKEYS.SESSION, {authToken: 'test-auth-token'}); - return waitForPromisesToResolve().then(() => { + return waitForBatchedUpdates().then(() => { // Then we should expect the request to have been made since the network is now ready expect(spyHttpUtilsXhr).not.toHaveBeenCalled(); }); @@ -230,16 +228,16 @@ describe('NetworkTests', () => { // Given a non-retryable request (that is bound to fail) const promise = Network.post('Get'); - return waitForPromisesToResolve() + return waitForBatchedUpdates() .then(() => { // When network connection is recovered Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Advance the network request queue by 1 second so that it can realize it's back online jest.advanceTimersByTime(CONST.NETWORK.PROCESS_REQUEST_DELAY_MS); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // Then the request should only have been attempted once and we should get an unable to retry @@ -259,7 +257,7 @@ describe('NetworkTests', () => { return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}) .then(() => { Network.post('MockBadNetworkResponse', {param1: 'value1'}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { expect(logHmmmSpy).toHaveBeenCalled(); @@ -275,7 +273,7 @@ describe('NetworkTests', () => { return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}) .then(() => { Network.post('MockBadNetworkResponse', {param1: 'value1'}); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { expect(logAlertSpy).toHaveBeenCalled(); @@ -294,7 +292,7 @@ describe('NetworkTests', () => { // When network calls with are made Network.post('mock command', {param1: 'value1'}).then(onResolved); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { const response = onResolved.mock.calls[0][0]; @@ -317,7 +315,7 @@ describe('NetworkTests', () => { Network.post('MockCommandThree'); // WHEN we wait for the requests to all cancel - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { // THEN expect our queue to be empty and for no requests to have been retried diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index f1a251e4e433..9f99838c6917 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -3,7 +3,7 @@ import Onyx from 'react-native-onyx'; import * as OptionsListUtils from '../../src/libs/OptionsListUtils'; import * as ReportUtils from '../../src/libs/ReportUtils'; import ONYXKEYS from '../../src/ONYXKEYS'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import CONST from '../../src/CONST'; describe('OptionsListUtils', () => { @@ -280,7 +280,7 @@ describe('OptionsListUtils', () => { }, }); Onyx.registerLogger(() => {}); - return waitForPromisesToResolve().then(() => Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, PERSONAL_DETAILS)); + return waitForBatchedUpdates().then(() => Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, PERSONAL_DETAILS)); }); it('getSearchOptions()', () => { @@ -308,7 +308,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports[0].text).toBe('Mister Fantastic'); expect(results.recentReports[1].text).toBe('Mister Fantastic'); - return waitForPromisesToResolve() + return waitForBatchedUpdates() .then(() => Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, PERSONAL_DETAILS_WITH_PERIODS)) .then(() => { // When we filter again but provide a searchValue that should match with periods diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index e97e9147c328..eeef96bf2102 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -3,7 +3,7 @@ import _ from 'underscore'; import CONST from '../../src/CONST'; import ONYXKEYS from '../../src/ONYXKEYS'; import * as ReportUtils from '../../src/libs/ReportUtils'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import * as LHNTestUtils from '../utils/LHNTestUtils'; // Be sure to include the mocked permissions library or else the beta tests won't work @@ -51,9 +51,9 @@ describe('ReportUtils', () => { [ONYXKEYS.COUNTRY_CODE]: 1, [`${ONYXKEYS.COLLECTION.POLICY}${policy.policyID}`]: policy, }); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }); - beforeEach(() => Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.DEFAULT).then(waitForPromisesToResolve)); + beforeEach(() => Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.DEFAULT).then(waitForBatchedUpdates)); describe('getDisplayNamesWithTooltips', () => { test('withSingleParticipantReport', () => { diff --git a/tests/unit/RequestTest.js b/tests/unit/RequestTest.js index 07943732030d..fb1032e70cfe 100644 --- a/tests/unit/RequestTest.js +++ b/tests/unit/RequestTest.js @@ -1,5 +1,5 @@ import * as Request from '../../src/libs/Request'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import * as TestHelper from '../utils/TestHelper'; beforeAll(() => { @@ -19,7 +19,7 @@ test('Request.use() can register a middleware and it will run', () => { }; Request.processWithMiddleware(request, true); - return waitForPromisesToResolve().then(() => { + return waitForBatchedUpdates().then(() => { const [promise, returnedRequest, isFromSequentialQueue] = testMiddleware.mock.calls[0]; expect(testMiddleware).toHaveBeenCalled(); expect(returnedRequest).toEqual(request); @@ -57,7 +57,7 @@ test('Request.use() can register two middlewares. They can pass a response to th const catchHandler = jest.fn(); Request.processWithMiddleware(request).catch(catchHandler); - return waitForPromisesToResolve().then(() => { + return waitForBatchedUpdates().then(() => { expect(catchHandler).toHaveBeenCalled(); expect(catchHandler).toHaveBeenCalledWith(new Error('Oops')); }); diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js index 85d409969133..18e499d89293 100644 --- a/tests/unit/SidebarFilterTest.js +++ b/tests/unit/SidebarFilterTest.js @@ -2,8 +2,8 @@ import {cleanup, screen} from '@testing-library/react-native'; import Onyx from 'react-native-onyx'; import lodashGet from 'lodash/get'; import * as LHNTestUtils from '../utils/LHNTestUtils'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; -import wrapOnyxWithWaitForPromisesToResolve from '../utils/wrapOnyxWithWaitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; import CONST from '../../src/CONST'; import DateUtils from '../../src/libs/DateUtils'; import * as Localize from '../../src/libs/Localize'; @@ -35,16 +35,16 @@ describe('Sidebar', () => { ); beforeEach(() => { - // Wrap Onyx each onyx action with waitForPromiseToResolve - wrapOnyxWithWaitForPromisesToResolve(Onyx); + // Wrap Onyx each onyx action with waitForBatchedUpdates + wrapOnyxWithWaitForBatchedUpdates(Onyx); // Initialize the network key for OfflineWithFeedback - Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); + return Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); }); // Cleanup (ie. unmount) all rendered components and clear out Onyx after each test so that each test starts with a clean slate afterEach(() => { cleanup(); - Onyx.clear(); + return Onyx.clear(); }); describe('in default (most recent) mode', () => { @@ -55,7 +55,7 @@ describe('Sidebar', () => { const report = LHNTestUtils.getFakeReport([]); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that report .then(() => Onyx.multiSet({ @@ -79,7 +79,7 @@ describe('Sidebar', () => { const report = LHNTestUtils.getFakeReport(['emptychat+1@test.com', 'emptychat+2@test.com'], 0); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that report .then(() => Onyx.multiSet({ @@ -107,7 +107,7 @@ describe('Sidebar', () => { }; return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that report .then(() => Onyx.multiSet({ @@ -137,7 +137,7 @@ describe('Sidebar', () => { }; return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -190,7 +190,7 @@ describe('Sidebar', () => { }; return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -242,7 +242,7 @@ describe('Sidebar', () => { }; return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -330,7 +330,7 @@ describe('Sidebar', () => { LHNTestUtils.getDefaultRenderedSidebarLinks(report1.reportID); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -375,7 +375,7 @@ describe('Sidebar', () => { LHNTestUtils.getDefaultRenderedSidebarLinks(report1.reportID); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -421,7 +421,7 @@ describe('Sidebar', () => { // When report 2 becomes the active report .then(() => { LHNTestUtils.getDefaultRenderedSidebarLinks(report2.reportID); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) // Then report 1 should now disappear @@ -446,7 +446,7 @@ describe('Sidebar', () => { LHNTestUtils.getDefaultRenderedSidebarLinks(draftReport.reportID); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -493,7 +493,7 @@ describe('Sidebar', () => { const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -556,7 +556,7 @@ describe('Sidebar', () => { const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -655,7 +655,7 @@ describe('Sidebar', () => { LHNTestUtils.getDefaultRenderedSidebarLinks(report1.reportID); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -707,7 +707,7 @@ describe('Sidebar', () => { const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -758,7 +758,7 @@ describe('Sidebar', () => { const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -807,7 +807,7 @@ describe('Sidebar', () => { const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -852,7 +852,7 @@ describe('Sidebar', () => { const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -874,7 +874,7 @@ describe('Sidebar', () => { // When sidebar is rendered with the active report ID matching the archived report in Onyx .then(() => { LHNTestUtils.getDefaultRenderedSidebarLinks(report.reportID); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) // Then the report is rendered in the LHN diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js index c3942f24e626..4a693d679b86 100644 --- a/tests/unit/SidebarOrderTest.js +++ b/tests/unit/SidebarOrderTest.js @@ -1,8 +1,8 @@ import Onyx from 'react-native-onyx'; import {cleanup, screen} from '@testing-library/react-native'; import lodashGet from 'lodash/get'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; -import wrapOnyxWithWaitForPromisesToResolve from '../utils/wrapOnyxWithWaitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; import * as LHNTestUtils from '../utils/LHNTestUtils'; import CONST from '../../src/CONST'; import DateUtils from '../../src/libs/DateUtils'; @@ -36,8 +36,8 @@ describe('Sidebar', () => { ); beforeEach(() => { - // Wrap Onyx each onyx action with waitForPromiseToResolve - wrapOnyxWithWaitForPromisesToResolve(Onyx); + // Wrap Onyx each onyx action with waitForBatchedUpdates + wrapOnyxWithWaitForBatchedUpdates(Onyx); // Initialize the network key for OfflineWithFeedback return Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); }); @@ -64,7 +64,7 @@ describe('Sidebar', () => { LHNTestUtils.getDefaultRenderedSidebarLinks(); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with some personal details .then(() => Onyx.multiSet({ @@ -88,7 +88,7 @@ describe('Sidebar', () => { LHNTestUtils.getDefaultRenderedSidebarLinks(report.reportID); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -120,7 +120,7 @@ describe('Sidebar', () => { Report.addComment(report3.reportID, 'Hi, this is a comment'); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -166,7 +166,7 @@ describe('Sidebar', () => { LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -209,7 +209,7 @@ describe('Sidebar', () => { Report.addComment(report3.reportID, 'Hi, this is a comment'); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -262,7 +262,7 @@ describe('Sidebar', () => { LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -280,7 +280,7 @@ describe('Sidebar', () => { // The changing of a route itself will re-render the component in the App, but since we are not performing this test // inside the navigator and it has no access to the routes we need to trigger an update to the SidebarLinks manually. LHNTestUtils.getDefaultRenderedSidebarLinks('1'); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) // Then the order of the reports should be 2 > 3 > 1 @@ -307,7 +307,7 @@ describe('Sidebar', () => { }; return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -344,7 +344,7 @@ describe('Sidebar', () => { }; return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -406,7 +406,7 @@ describe('Sidebar', () => { LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -458,7 +458,7 @@ describe('Sidebar', () => { }; LHNTestUtils.getDefaultRenderedSidebarLinks('0'); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -518,7 +518,7 @@ describe('Sidebar', () => { }; LHNTestUtils.getDefaultRenderedSidebarLinks('0'); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -577,7 +577,7 @@ describe('Sidebar', () => { const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; LHNTestUtils.getDefaultRenderedSidebarLinks('0'); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -614,7 +614,7 @@ describe('Sidebar', () => { const report4 = LHNTestUtils.getFakeReport([7, 8], 0, true); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // Given the sidebar is rendered in #focus mode (hides read chats) // with all reports having unread comments .then(() => @@ -669,7 +669,7 @@ describe('Sidebar', () => { const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; LHNTestUtils.getDefaultRenderedSidebarLinks('0'); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -788,7 +788,7 @@ describe('Sidebar', () => { const currentlyLoggedInUserAccountID = 13; LHNTestUtils.getDefaultRenderedSidebarLinks('0'); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -846,7 +846,7 @@ describe('Sidebar', () => { LHNTestUtils.getDefaultRenderedSidebarLinks('0'); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ diff --git a/tests/unit/SidebarTest.js b/tests/unit/SidebarTest.js index 84403ce5fc11..1b5daa323da5 100644 --- a/tests/unit/SidebarTest.js +++ b/tests/unit/SidebarTest.js @@ -1,8 +1,8 @@ import Onyx from 'react-native-onyx'; import {cleanup, screen} from '@testing-library/react-native'; import lodashGet from 'lodash/get'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; -import wrapOnyxWithWaitForPromisesToResolve from '../utils/wrapOnyxWithWaitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; import * as LHNTestUtils from '../utils/LHNTestUtils'; import CONST from '../../src/CONST'; import * as Localize from '../../src/libs/Localize'; @@ -34,8 +34,8 @@ describe('Sidebar', () => { ); beforeEach(() => { - // Wrap Onyx each onyx action with waitForPromiseToResolve - wrapOnyxWithWaitForPromisesToResolve(Onyx); + // Wrap Onyx each onyx action with waitForBatchedUpdates + wrapOnyxWithWaitForBatchedUpdates(Onyx); // Initialize the network key for OfflineWithFeedback return Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); }); @@ -59,7 +59,7 @@ describe('Sidebar', () => { const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; LHNTestUtils.getDefaultRenderedSidebarLinks('0'); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -102,7 +102,7 @@ describe('Sidebar', () => { const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; LHNTestUtils.getDefaultRenderedSidebarLinks('0'); return ( - waitForPromisesToResolve() + waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ diff --git a/tests/unit/enhanceParametersTest.js b/tests/unit/enhanceParametersTest.js index 8dc625ba6d23..fb2ccc86ad79 100644 --- a/tests/unit/enhanceParametersTest.js +++ b/tests/unit/enhanceParametersTest.js @@ -1,7 +1,7 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../src/ONYXKEYS'; import enhanceParameters from '../../src/libs/Network/enhanceParameters'; -import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import CONFIG from '../../src/CONFIG'; beforeEach(() => Onyx.clear()); @@ -12,7 +12,7 @@ test('Enhance parameters adds correct parameters for Log command with no authTok const email = 'test-user@test.com'; const authToken = 'test-token'; Onyx.merge(ONYXKEYS.SESSION, {email, authToken}); - return waitForPromisesToResolve().then(() => { + return waitForBatchedUpdates().then(() => { const finalParameters = enhanceParameters(command, parameters); expect(finalParameters).toEqual({ testParameter: 'test', @@ -30,7 +30,7 @@ test('Enhance parameters adds correct parameters for a command that requires aut const email = 'test-user@test.com'; const authToken = 'test-token'; Onyx.merge(ONYXKEYS.SESSION, {email, authToken}); - return waitForPromisesToResolve().then(() => { + return waitForBatchedUpdates().then(() => { const finalParameters = enhanceParameters(command, parameters); expect(finalParameters).toEqual({ testParameter: 'test', diff --git a/tests/utils/TestHelper.js b/tests/utils/TestHelper.js index 05f40b465b68..ca3955e9eb90 100644 --- a/tests/utils/TestHelper.js +++ b/tests/utils/TestHelper.js @@ -5,7 +5,7 @@ import CONST from '../../src/CONST'; import * as Session from '../../src/libs/actions/Session'; import HttpUtils from '../../src/libs/HttpUtils'; import ONYXKEYS from '../../src/ONYXKEYS'; -import waitForPromisesToResolve from './waitForPromisesToResolve'; +import waitForBatchedUpdates from './waitForBatchedUpdates'; import * as NumberUtils from '../../src/libs/NumberUtils'; /** @@ -72,7 +72,7 @@ function signInWithTestUser(accountID = 1, login = 'test@user.com', password = ' // Simulate user entering their login and populating the credentials.login Session.beginSignIn(login); - return waitForPromisesToResolve() + return waitForBatchedUpdates() .then(() => { // Response is the same for calls to Authenticate and BeginSignIn HttpUtils.xhr.mockResolvedValue({ @@ -116,7 +116,7 @@ function signInWithTestUser(accountID = 1, login = 'test@user.com', password = ' jsonCode: 200, }); Session.signIn(password); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }) .then(() => { HttpUtils.xhr = originalXhr; @@ -128,7 +128,7 @@ function signOutTestUser() { HttpUtils.xhr = jest.fn(); HttpUtils.xhr.mockResolvedValue({jsonCode: 200}); Session.signOutAndRedirectToSignIn(); - return waitForPromisesToResolve().then(() => (HttpUtils.xhr = originalXhr)); + return waitForBatchedUpdates().then(() => (HttpUtils.xhr = originalXhr)); } /** @@ -174,7 +174,7 @@ function getGlobalFetchMock() { mockFetch.resume = () => { isPaused = false; _.each(queue, (resolve) => resolve(getResponse())); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); }; mockFetch.fail = () => (shouldFail = true); mockFetch.succeed = () => (shouldFail = false); @@ -191,7 +191,7 @@ function setPersonalDetails(login, accountID) { Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { [accountID]: buildPersonalDetails(login, accountID), }); - return waitForPromisesToResolve(); + return waitForBatchedUpdates(); } /** diff --git a/tests/utils/getIsUsingFakeTimers.js b/tests/utils/getIsUsingFakeTimers.js new file mode 100644 index 000000000000..376312ac6c06 --- /dev/null +++ b/tests/utils/getIsUsingFakeTimers.js @@ -0,0 +1 @@ +export default () => Boolean(global.setTimeout.mock || global.setTimeout.clock); diff --git a/tests/utils/waitForBatchedUpdates.js b/tests/utils/waitForBatchedUpdates.js new file mode 100644 index 000000000000..2c4dbec250bc --- /dev/null +++ b/tests/utils/waitForBatchedUpdates.js @@ -0,0 +1,39 @@ +import getIsUsingFakeTimers from './getIsUsingFakeTimers'; +/** + * Method which waits for all asynchronous JS to stop executing before proceeding. This helps test things like actions + * that expect some Onyx value to be available. This way we do not have to explicitly wait for an action to finish + * (e.g. by making it a promise and waiting for it to resolve). + * + * **Note:** It is recommended to wait for the Onyx operations, so in your tests its preferred to do: + * ✅ Onyx.merge(...).then(...) + * than to do + * ❌ Onyx.merge(...) + * waitForBatchedUpdates().then(...) + * + * @returns {Promise} + */ +export default () => + new Promise((outerResolve) => { + // We first need to exhaust the microtask queue, before we schedule the next task in the macrotask queue (setTimeout). + // This is because we need to wait for all async onyx operations to finish, as they might schedule other macrotasks, + // and we want those task to run before our scheduled task. + // E.g. this makes the following code work for tests: + // + // Onyx.merge(...) + // return waitForBatchedUpdates().then(...); + // + // Note: Ideally, you'd just await the Onyx.merge promise. + + new Promise((innerResolve) => { + setImmediate(() => { + innerResolve("Flush all micro tasks that pushed by using '.then' method"); + }); + }).then(() => { + if (getIsUsingFakeTimers()) { + jest.runOnlyPendingTimers(); + outerResolve(); + return; + } + setTimeout(outerResolve, 0); + }); + }); diff --git a/tests/utils/waitForPromisesToResolveWithAct.js b/tests/utils/waitForBatchedUpdatesWithAct.js similarity index 84% rename from tests/utils/waitForPromisesToResolveWithAct.js rename to tests/utils/waitForBatchedUpdatesWithAct.js index eaef0f3b1a9d..125cf74159b3 100644 --- a/tests/utils/waitForPromisesToResolveWithAct.js +++ b/tests/utils/waitForBatchedUpdatesWithAct.js @@ -1,5 +1,5 @@ import {act} from '@testing-library/react-native'; -import waitForPromisesToResolve from './waitForPromisesToResolve'; +import waitForBatchedUpdates from './waitForBatchedUpdates'; /** * This method is necessary because react-navigation's NavigationContainer makes an internal state update when parsing the @@ -18,16 +18,16 @@ import waitForPromisesToResolve from './waitForPromisesToResolve'; * * When not to use this: * - * - You're not rendering any react components at all in your tests, but have some async logic you need to wait for e.g. Onyx.merge(). Use waitForPromisesToResolve(). - * - You're writing UI tests but don't see any errors or warnings related to using act(). You probably don't need this in that case and should use waitForPromisesToResolve(). + * - You're not rendering any react components at all in your tests, but have some async logic you need to wait for e.g. Onyx.merge(). Use waitForBatchedUpdates(). + * - You're writing UI tests but don't see any errors or warnings related to using act(). You probably don't need this in that case and should use waitForBatchedUpdates(). * - You're writing UI test and do see a warning about using act(), but there's no asynchronous code that needs to run inside act(). * * @returns {Promise} */ // eslint-disable-next-line @lwc/lwc/no-async-await -export default async function waitForPromisesToResolveWithAct() { +export default async function waitForBatchedUpdatesWithAct() { // eslint-disable-next-line @lwc/lwc/no-async-await await act(async () => { - await waitForPromisesToResolve(); + await waitForBatchedUpdates(); }); } diff --git a/tests/utils/waitForNetworkPromises.js b/tests/utils/waitForNetworkPromises.js new file mode 100644 index 000000000000..a60061d597e3 --- /dev/null +++ b/tests/utils/waitForNetworkPromises.js @@ -0,0 +1,14 @@ +import waitForBatchedUpdates from './waitForBatchedUpdates'; + +/** + * Method flushes microtasks and pending timers twice. Because we batch onyx updates + * Network requests takes 2 microtask cycles to resolve + * **Note:** It is recommended to wait for the Onyx operations, so in your tests its preferred to do: + * ✅ Onyx.merge(...).then(...) + * than to do + * ❌ Onyx.merge(...) + * waitForBatchedUpdates().then(...) + * + * @returns {Promise} + */ +export default () => waitForBatchedUpdates().then(waitForBatchedUpdates); diff --git a/tests/utils/waitForPromisesToResolve.js b/tests/utils/waitForPromisesToResolve.js deleted file mode 100644 index 97146e6a8d86..000000000000 --- a/tests/utils/waitForPromisesToResolve.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Method which waits for all asynchronous JS to stop executing before proceeding. This helps test things like actions - * that expect some Onyx value to be available. This way we do not have to explicitly wait for an action to finish - * (e.g. by making it a promise and waiting for it to resolve). - * - * @returns {Promise} - */ -export default () => new Promise(setImmediate); diff --git a/tests/utils/wrapOnyxWithWaitForPromisesToResolve.js b/tests/utils/wrapOnyxWithWaitForBatchedUpdates.js similarity index 73% rename from tests/utils/wrapOnyxWithWaitForPromisesToResolve.js rename to tests/utils/wrapOnyxWithWaitForBatchedUpdates.js index c560c50538bd..e5b6e6bfdfcf 100644 --- a/tests/utils/wrapOnyxWithWaitForPromisesToResolve.js +++ b/tests/utils/wrapOnyxWithWaitForBatchedUpdates.js @@ -1,4 +1,4 @@ -import waitForPromisesToResolve from './waitForPromisesToResolve'; +import waitForBatchedUpdates from './waitForBatchedUpdates'; /** * When we change data in onyx, the listeners (components) will be notified @@ -10,11 +10,11 @@ import waitForPromisesToResolve from './waitForPromisesToResolve'; * * @param {Object} onyxInstance */ -export default function wrapOnyxWithWaitForPromisesToResolve(onyxInstance) { +export default function wrapOnyxWithWaitForBatchedUpdates(onyxInstance) { const multiSetImpl = onyxInstance.multiSet; // eslint-disable-next-line no-param-reassign - onyxInstance.multiSet = (...args) => multiSetImpl(...args).then((result) => waitForPromisesToResolve().then(() => result)); + onyxInstance.multiSet = (...args) => multiSetImpl(...args).then((result) => waitForBatchedUpdates().then(() => result)); const mergeImpl = onyxInstance.merge; // eslint-disable-next-line no-param-reassign - onyxInstance.merge = (...args) => mergeImpl(...args).then((result) => waitForPromisesToResolve().then(() => result)); + onyxInstance.merge = (...args) => mergeImpl(...args).then((result) => waitForBatchedUpdates().then(() => result)); }