diff --git a/android/app/build.gradle b/android/app/build.gradle
index c7fbf56f86e6..0f69346a44bc 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -107,8 +107,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1009000303
- versionName "9.0.3-3"
+ versionCode 1009000305
+ versionName "9.0.3-5"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index f400a6f43605..ad8cc982e052 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 9.0.3.3
+ 9.0.3.5
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index b800b9f73118..8fc553fe8c0c 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 9.0.3.3
+ 9.0.3.5
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 7e562b7de1f2..83e4d904584b 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString
9.0.3
CFBundleVersion
- 9.0.3.3
+ 9.0.3.5
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 35dccc2de393..a5ffdcb4b63c 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1243,7 +1243,13 @@ PODS:
- react-native-config (1.5.0):
- react-native-config/App (= 1.5.0)
- react-native-config/App (1.5.0):
- - React-Core
+ - RCT-Folly
+ - RCTRequired
+ - RCTTypeSafety
+ - React
+ - React-Codegen
+ - React-RCTFabric
+ - ReactCommon/turbomodule/core
- react-native-document-picker (9.1.1):
- RCT-Folly
- RCTRequired
@@ -1974,7 +1980,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- - RNScreens (3.30.1):
+ - RNScreens (3.32.0):
- glog
- hermes-engine
- RCT-Folly (= 2022.05.16.00)
@@ -1988,13 +1994,14 @@ PODS:
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
+ - React-RCTImage
- React-rendererdebug
- React-utils
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- - RNScreens/common (= 3.30.1)
+ - RNScreens/common (= 3.32.0)
- Yoga
- - RNScreens/common (3.30.1):
+ - RNScreens/common (3.32.0):
- glog
- hermes-engine
- RCT-Folly (= 2022.05.16.00)
@@ -2008,6 +2015,7 @@ PODS:
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
+ - React-RCTImage
- React-rendererdebug
- React-utils
- ReactCommon/turbomodule/bridging
@@ -2552,7 +2560,7 @@ SPEC CHECKSUMS:
react-native-airship: 38e2596999242b68c933959d6145512e77937ac0
react-native-blob-util: 1ddace5234c62e3e6e4e154d305ad07ef686599b
react-native-cameraroll: f373bebbe9f6b7c3fd2a6f97c5171cda574cf957
- react-native-config: 5330c8258265c1e5fdb8c009d2cabd6badd96727
+ react-native-config: 5ce986133b07fc258828b20b9506de0e683efc1c
react-native-document-picker: 8532b8af7c2c930f9e202aac484ac785b0f4f809
react-native-geolocation: f9e92eb774cb30ac1e099f34b3a94f03b4db7eb3
react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440
@@ -2612,7 +2620,7 @@ SPEC CHECKSUMS:
RNPermissions: 0b61d30d21acbeafe25baaa47d9bae40a0c65216
RNReactNativeHapticFeedback: 616c35bdec7d20d4c524a7949ca9829c09e35f37
RNReanimated: 323436b1a5364dca3b5f8b1a13458455e0de9efe
- RNScreens: 9ec969a95987a6caae170ef09313138abf3331e1
+ RNScreens: abd354e98519ed267600b7ee64fdcb8e060b1218
RNShare: 2a4cdfc0626ad56b0ef583d424f2038f772afe58
RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852
RNSVG: 18f1381e046be2f1c30b4724db8d0c966238089f
diff --git a/package-lock.json b/package-lock.json
index 07b13b5f078a..e9cc310d29e9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "9.0.3-3",
+ "version": "9.0.3-5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.0.3-3",
+ "version": "9.0.3-5",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -115,7 +115,7 @@
"react-native-release-profiler": "^0.1.6",
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "4.8.2",
- "react-native-screens": "3.30.1",
+ "react-native-screens": "3.32.0",
"react-native-share": "^10.0.2",
"react-native-sound": "^0.11.2",
"react-native-svg": "14.1.0",
@@ -37506,9 +37506,9 @@
}
},
"node_modules/react-native-screens": {
- "version": "3.30.1",
- "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.30.1.tgz",
- "integrity": "sha512-/muEvjocCtFb+j5J3YmLvB25+f4rIU8hnnxgGTkXcAf2omPBY8uhPjJaaFUlvj64VEoEzJcRpugbXWsjfPPIFg==",
+ "version": "3.32.0",
+ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.32.0.tgz",
+ "integrity": "sha512-wybqZAHX7v8ipOXhh90CqGLkBHw5JYqKNRBX7R/b0c2WQisTOgu0M0yGwBMM6LyXRBT+4k3NTGHdDbpJVpq0yQ==",
"dependencies": {
"react-freeze": "^1.0.0",
"warn-once": "^0.1.0"
diff --git a/package.json b/package.json
index ddb75aae9700..e5e8f8fa8e60 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.0.3-3",
+ "version": "9.0.3-5",
"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.",
@@ -168,7 +168,7 @@
"react-native-release-profiler": "^0.1.6",
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "4.8.2",
- "react-native-screens": "3.30.1",
+ "react-native-screens": "3.32.0",
"react-native-share": "^10.0.2",
"react-native-sound": "^0.11.2",
"react-native-svg": "14.1.0",
diff --git a/patches/@expensify+react-native-live-markdown+0.1.88.patch b/patches/@expensify+react-native-live-markdown+0.1.85.patch
similarity index 100%
rename from patches/@expensify+react-native-live-markdown+0.1.88.patch
rename to patches/@expensify+react-native-live-markdown+0.1.85.patch
diff --git a/patches/react-native-keyboard-controller+1.12.2.patch b/patches/react-native-keyboard-controller+1.12.2.patch.patch
similarity index 100%
rename from patches/react-native-keyboard-controller+1.12.2.patch
rename to patches/react-native-keyboard-controller+1.12.2.patch.patch
diff --git a/patches/react-native-screens+3.30.1+001+fix-screen-type.patch b/patches/react-native-screens+3.30.1+001+fix-screen-type.patch
deleted file mode 100644
index f282ec58b07b..000000000000
--- a/patches/react-native-screens+3.30.1+001+fix-screen-type.patch
+++ /dev/null
@@ -1,12 +0,0 @@
-diff --git a/node_modules/react-native-screens/src/components/Screen.tsx b/node_modules/react-native-screens/src/components/Screen.tsx
-index 3f9a1cb..45767f7 100644
---- a/node_modules/react-native-screens/src/components/Screen.tsx
-+++ b/node_modules/react-native-screens/src/components/Screen.tsx
-@@ -79,6 +79,7 @@ export class InnerScreen extends React.Component {
- // Due to how Yoga resolves layout, we need to have different components for modal nad non-modal screens
- const AnimatedScreen =
- Platform.OS === 'android' ||
-+ stackPresentation === undefined ||
- stackPresentation === 'push' ||
- stackPresentation === 'containedModal' ||
- stackPresentation === 'containedTransparentModal'
diff --git a/scripts/applyPatches.sh b/scripts/applyPatches.sh
deleted file mode 100755
index 31e1b7f50e6a..000000000000
--- a/scripts/applyPatches.sh
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/bash
-
-# This script is a simple wrapper around patch-package that fails if any errors or warnings are detected.
-# This is useful because patch-package does not fail on errors or warnings by default,
-# which means that broken patches are easy to miss, and leads to developer frustration and wasted time.
-
-SCRIPTS_DIR=$(dirname "${BASH_SOURCE[0]}")
-source "$SCRIPTS_DIR/shellUtils.sh"
-
-# Run patch-package and capture its output and exit code, while still displaying the original output to the terminal
-# (we use `script -q /dev/null` to preserve colorization in the output)
-TEMP_OUTPUT="$(mktemp)"
-script -q /dev/null npx patch-package --error-on-fail 2>&1 | tee "$TEMP_OUTPUT"
-EXIT_CODE=${PIPESTATUS[0]}
-OUTPUT="$(cat "$TEMP_OUTPUT")"
-rm -f "$TEMP_OUTPUT"
-
-# Check if the output contains a warning message
-echo "$OUTPUT" | grep -q "Warning:"
-WARNING_FOUND=$?
-
-printf "\n";
-
-# Determine the final exit code
-if [ "$EXIT_CODE" -eq 0 ]; then
- if [ $WARNING_FOUND -eq 0 ]; then
- # patch-package succeeded but warning was found
- error "It looks like you upgraded a dependency without upgrading the patch. Please review the patch, determine if it's still needed, and port it to the new version of the dependency."
- exit 1
- else
- # patch-package succeeded and no warning was found
- success "patch-package succeeded without errors or warnings"
- exit 0
- fi
-else
- # patch-package failed
- error "patch-package failed to apply a patch"
- exit "$EXIT_CODE"
-fi
diff --git a/scripts/postInstall.sh b/scripts/postInstall.sh
index 782c8ef5822c..339fdf25cb10 100755
--- a/scripts/postInstall.sh
+++ b/scripts/postInstall.sh
@@ -1,14 +1,11 @@
#!/bin/bash
-# Exit immediately if any command exits with a non-zero status
-set -e
-
# Go to project root
ROOT_DIR=$(dirname "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)")
cd "$ROOT_DIR" || exit 1
-# Apply packages using patch-package
-scripts/applyPatches.sh
+# Run patch-package
+npx patch-package
# Install node_modules in subpackages, unless we're in a CI/CD environment,
# where the node_modules for subpackages are cached separately.
diff --git a/src/hooks/usePaginatedReportActions.ts b/src/hooks/usePaginatedReportActions.ts
deleted file mode 100644
index 44a82253b7c0..000000000000
--- a/src/hooks/usePaginatedReportActions.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import {useMemo} from 'react';
-import {useOnyx} from 'react-native-onyx';
-import * as ReportActionsUtils from '@libs/ReportActionsUtils';
-import ONYXKEYS from '@src/ONYXKEYS';
-
-/**
- * Get the longest continuous chunk of reportActions including the linked reportAction. If not linking to a specific action, returns the continuous chunk of newest reportActions.
- */
-function usePaginatedReportActions(reportID?: string, reportActionID?: string) {
- // Use `||` instead of `??` to handle empty string.
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- const reportIDWithDefault = reportID || '-1';
- const [sortedAllReportActions = []] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportIDWithDefault}`, {
- canEvict: false,
- selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, true),
- });
-
- const reportActions = useMemo(() => {
- if (!sortedAllReportActions.length) {
- return [];
- }
- return ReportActionsUtils.getContinuousReportActionChain(sortedAllReportActions, reportActionID);
- }, [reportActionID, sortedAllReportActions]);
-
- const linkedAction = useMemo(() => sortedAllReportActions.find((obj) => String(obj.reportActionID) === String(reportActionID)), [reportActionID, sortedAllReportActions]);
-
- return {
- reportActions,
- linkedAction,
- };
-}
-
-export default usePaginatedReportActions;
diff --git a/src/libs/API/parameters/OpenPolicyInitialPageParams.ts b/src/libs/API/parameters/OpenPolicyInitialPageParams.ts
deleted file mode 100644
index 764abe9a6a77..000000000000
--- a/src/libs/API/parameters/OpenPolicyInitialPageParams.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-type OpenPolicyInitialPageParams = {
- policyID: string;
-};
-
-export default OpenPolicyInitialPageParams;
diff --git a/src/libs/API/parameters/OpenPolicyProfilePageParams.ts b/src/libs/API/parameters/OpenPolicyProfilePageParams.ts
deleted file mode 100644
index 55dce33a3dac..000000000000
--- a/src/libs/API/parameters/OpenPolicyProfilePageParams.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-type OpenPolicyProfilePageParams = {
- policyID: string;
-};
-
-export default OpenPolicyProfilePageParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index ef44ce06bced..f032edf96e36 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -193,8 +193,6 @@ export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDis
export type {default as OpenPolicyTaxesPageParams} from './OpenPolicyTaxesPageParams';
export type {default as EnablePolicyTaxesParams} from './EnablePolicyTaxesParams';
export type {default as OpenPolicyMoreFeaturesPageParams} from './OpenPolicyMoreFeaturesPageParams';
-export type {default as OpenPolicyProfilePageParams} from './OpenPolicyProfilePageParams';
-export type {default as OpenPolicyInitialPageParams} from './OpenPolicyInitialPageParams';
export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams';
export type {default as SetPolicyDistanceRatesUnitParams} from './SetPolicyDistanceRatesUnitParams';
export type {default as SetPolicyDistanceRatesDefaultCategoryParams} from './SetPolicyDistanceRatesDefaultCategoryParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 062f0e915873..1d6456f3df47 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -537,8 +537,6 @@ const READ_COMMANDS = {
OPEN_POLICY_WORKFLOWS_PAGE: 'OpenPolicyWorkflowsPage',
OPEN_POLICY_DISTANCE_RATES_PAGE: 'OpenPolicyDistanceRatesPage',
OPEN_POLICY_MORE_FEATURES_PAGE: 'OpenPolicyMoreFeaturesPage',
- OPEN_POLICY_PROFILE_PAGE: 'OpenPolicyProfilePage',
- OPEN_POLICY_INITIAL_PAGE: 'OpenPolicyInitialPage',
OPEN_POLICY_ACCOUNTING_PAGE: 'OpenPolicyAccountingPage',
SEARCH: 'Search',
OPEN_SUBSCRIPTION_PAGE: 'OpenSubscriptionPage',
@@ -587,8 +585,6 @@ type ReadCommandParameters = {
[READ_COMMANDS.OPEN_POLICY_WORKFLOWS_PAGE]: Parameters.OpenPolicyWorkflowsPageParams;
[READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE]: Parameters.OpenPolicyDistanceRatesPageParams;
[READ_COMMANDS.OPEN_POLICY_MORE_FEATURES_PAGE]: Parameters.OpenPolicyMoreFeaturesPageParams;
- [READ_COMMANDS.OPEN_POLICY_PROFILE_PAGE]: Parameters.OpenPolicyProfilePageParams;
- [READ_COMMANDS.OPEN_POLICY_INITIAL_PAGE]: Parameters.OpenPolicyInitialPageParams;
[READ_COMMANDS.OPEN_POLICY_ACCOUNTING_PAGE]: Parameters.OpenPolicyAccountingPageParams;
[READ_COMMANDS.SEARCH]: Parameters.SearchParams;
[READ_COMMANDS.OPEN_SUBSCRIPTION_PAGE]: null;
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index b17fe7266079..cf47864a779e 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -331,6 +331,7 @@ type OptimisticTaskReport = Pick<
| 'notificationPreference'
| 'parentReportActionID'
| 'lastVisibleActionCreated'
+ | 'hasParentAccess'
>;
type TransactionDetails = {
@@ -1080,7 +1081,7 @@ function doesReportBelongToWorkspace(report: OnyxEntry, policyMemberAcco
/**
* Given an array of reports, return them filtered by a policyID and policyMemberAccountIDs.
*/
-function filterReportsByPolicyIDAndMemberAccountIDs(reports: Report[], policyMemberAccountIDs: number[] = [], policyID?: string) {
+function filterReportsByPolicyIDAndMemberAccountIDs(reports: Array>, policyMemberAccountIDs: number[] = [], policyID?: string) {
return reports.filter((report) => !!report && doesReportBelongToWorkspace(report, policyMemberAccountIDs, policyID));
}
@@ -1166,6 +1167,7 @@ function findLastAccessedReport(
reportMetadata: OnyxCollection = {},
policyID?: string,
policyMemberAccountIDs: number[] = [],
+ excludeReportID?: string,
): OnyxEntry {
// If it's the user's first time using New Expensify, then they could either have:
// - just a Concierge report, if so we'll return that
@@ -1173,7 +1175,7 @@ function findLastAccessedReport(
// If it's the latter, we'll use the deeplinked report over the Concierge report,
// since the Concierge report would be incorrectly selected over the deep-linked report in the logic below.
- let reportsValues = Object.values(reports ?? {}) as Report[];
+ let reportsValues = Object.values(reports ?? {});
if (!!policyID || policyMemberAccountIDs.length > 0) {
reportsValues = filterReportsByPolicyIDAndMemberAccountIDs(reportsValues, policyMemberAccountIDs, policyID);
@@ -1189,13 +1191,28 @@ function findLastAccessedReport(
});
}
- if (ignoreDomainRooms) {
- // We allow public announce rooms, admins, and announce rooms through since we bypass the default rooms beta for them.
- // Check where ReportUtils.findLastAccessedReport is called in MainDrawerNavigator.js for more context.
- // Domain rooms are now the only type of default room that are on the defaultRooms beta.
- sortedReports = sortedReports.filter(
- (report) => !isDomainRoom(report) || getPolicyType(report, policies) === CONST.POLICY.TYPE.FREE || hasExpensifyGuidesEmails(Object.keys(report?.participants ?? {}).map(Number)),
- );
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ const shouldFilter = excludeReportID || ignoreDomainRooms;
+ if (shouldFilter) {
+ sortedReports = sortedReports.filter((report) => {
+ if (excludeReportID && report?.reportID === excludeReportID) {
+ return false;
+ }
+
+ // We allow public announce rooms, admins, and announce rooms through since we bypass the default rooms beta for them.
+ // Check where ReportUtils.findLastAccessedReport is called in MainDrawerNavigator.js for more context.
+ // Domain rooms are now the only type of default room that are on the defaultRooms beta.
+ if (
+ ignoreDomainRooms &&
+ isDomainRoom(report) &&
+ getPolicyType(report, policies) !== CONST.POLICY.TYPE.FREE &&
+ !hasExpensifyGuidesEmails(Object.keys(report?.participants ?? {}).map(Number))
+ ) {
+ return false;
+ }
+
+ return true;
+ });
}
if (isFirstTimeNewExpensifyUser) {
@@ -2279,7 +2296,15 @@ function getLastVisibleMessage(reportID: string | undefined, actionsToMerge: Rep
* @param [parentReportAction] - The parent report action of the report (Used to check if the task has been canceled)
*/
function isWaitingForAssigneeToCompleteTask(report: OnyxEntry, parentReportAction: OnyxEntry): boolean {
- return isTaskReport(report) && isReportManager(report) && isOpenTaskReport(report, parentReportAction);
+ if (report?.hasOutstandingChildTask) {
+ return true;
+ }
+
+ if (isOpenTaskReport(report, parentReportAction) && !report?.hasParentAccess && isReportManager(report)) {
+ return true;
+ }
+
+ return false;
}
function isUnreadWithMention(reportOrOption: OnyxEntry | OptionData): boolean {
@@ -5105,6 +5130,7 @@ function buildOptimisticTaskReport(
statusNum: CONST.REPORT.STATUS_NUM.OPEN,
notificationPreference,
lastVisibleActionCreated: DateUtils.getDBTime(),
+ hasParentAccess: true,
};
}
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index d48404349057..b7d365a103ae 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -245,6 +245,8 @@ function getOptionData({
searchText: undefined,
isPinned: false,
hasOutstandingChildRequest: false,
+ hasOutstandingChildTask: false,
+ hasParentAccess: undefined,
isIOUReportOwner: null,
isChatRoom: false,
isArchivedRoom: false,
@@ -298,6 +300,8 @@ function getOptionData({
result.isDeletedParentAction = report.isDeletedParentAction;
result.isSelfDM = ReportUtils.isSelfDM(report);
result.tooltipText = ReportUtils.getReportParticipantsTitle(visibleParticipantAccountIDs);
+ result.hasOutstandingChildTask = report.hasOutstandingChildTask;
+ result.hasParentAccess = report.hasParentAccess;
const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat || ReportUtils.isExpenseReport(report);
const subtitle = ReportUtils.getChatRoomSubtitle(report);
diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts
index b42b0c0d38a6..1bb53fbfa002 100644
--- a/src/libs/actions/Policy/Policy.ts
+++ b/src/libs/actions/Policy/Policy.ts
@@ -18,9 +18,7 @@ import type {
EnablePolicyWorkflowsParams,
LeavePolicyParams,
OpenDraftWorkspaceRequestParams,
- OpenPolicyInitialPageParams,
OpenPolicyMoreFeaturesPageParams,
- OpenPolicyProfilePageParams,
OpenPolicyTaxesPageParams,
OpenPolicyWorkflowsPageParams,
OpenWorkspaceInvitePageParams,
@@ -2823,18 +2821,6 @@ function openPolicyMoreFeaturesPage(policyID: string) {
API.read(READ_COMMANDS.OPEN_POLICY_MORE_FEATURES_PAGE, params);
}
-function openPolicyProfilePage(policyID: string) {
- const params: OpenPolicyProfilePageParams = {policyID};
-
- API.read(READ_COMMANDS.OPEN_POLICY_PROFILE_PAGE, params);
-}
-
-function openPolicyInitialPage(policyID: string) {
- const params: OpenPolicyInitialPageParams = {policyID};
-
- API.read(READ_COMMANDS.OPEN_POLICY_INITIAL_PAGE, params);
-}
-
function setPolicyCustomTaxName(policyID: string, customTaxName: string) {
const policy = getPolicy(policyID);
const originalCustomTaxName = policy?.taxRates?.name;
@@ -3042,8 +3028,6 @@ export {
enablePolicyWorkflows,
enableDistanceRequestTax,
openPolicyMoreFeaturesPage,
- openPolicyProfilePage,
- openPolicyInitialPage,
generateCustomUnitID,
clearQBOErrorField,
clearXeroErrorField,
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 3761f5b65e0d..31e801deeea4 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -961,6 +961,7 @@ function openReport(
function navigateToAndOpenReport(
userLogins: string[],
shouldDismissModal = true,
+ actionType?: string,
reportName?: string,
avatarUri?: string,
avatarFile?: File | CustomRNImageManipulatorResult | undefined,
@@ -1003,7 +1004,7 @@ function navigateToAndOpenReport(
Navigation.dismissModalWithReport(report);
} else {
Navigation.navigateWithSwitchPolicyID({route: ROUTES.HOME});
- Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report?.reportID ?? '-1'));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report?.reportID ?? '-1'), actionType);
}
}
@@ -2011,7 +2012,7 @@ function updateWriteCapabilityAndNavigate(report: Report, newValue: WriteCapabil
/**
* Navigates to the 1:1 report with Concierge
*/
-function navigateToConciergeChat(shouldDismissModal = false, checkIfCurrentPageActive = () => true) {
+function navigateToConciergeChat(shouldDismissModal = false, checkIfCurrentPageActive = () => true, actionType?: string) {
// If conciergeChatReportID contains a concierge report ID, we navigate to the concierge chat using the stored report ID.
// Otherwise, we would find the concierge chat and navigate to it.
if (!conciergeChatReportID) {
@@ -2022,12 +2023,12 @@ function navigateToConciergeChat(shouldDismissModal = false, checkIfCurrentPageA
if (!checkIfCurrentPageActive()) {
return;
}
- navigateToAndOpenReport([CONST.EMAIL.CONCIERGE], shouldDismissModal);
+ navigateToAndOpenReport([CONST.EMAIL.CONCIERGE], shouldDismissModal, actionType);
});
} else if (shouldDismissModal) {
Navigation.dismissModal(conciergeChatReportID);
} else {
- Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(conciergeChatReportID));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(conciergeChatReportID), actionType);
}
}
@@ -2555,40 +2556,20 @@ function getCurrentUserAccountID(): number {
}
function navigateToMostRecentReport(currentReport: OnyxEntry) {
- const reportID = currentReport?.reportID;
- const sortedReportsByLastRead = ReportUtils.sortReportsByLastRead(Object.values(allReports ?? {}) as Report[], reportMetadata);
-
- // We want to filter out the current report, hidden reports and empty chats
- const filteredReportsByLastRead = sortedReportsByLastRead.filter(
- (sortedReport) =>
- sortedReport?.reportID !== reportID &&
- sortedReport?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN &&
- ReportUtils.shouldReportBeInOptionList({
- report: sortedReport,
- currentReportId: '',
- isInFocusMode: false,
- betas: [],
- policies: {},
- excludeEmptyChats: true,
- doesReportHaveViolations: false,
- includeSelfDM: true,
- }),
- );
- const lastAccessedReportID = filteredReportsByLastRead.at(-1)?.reportID;
- const isChatThread = ReportUtils.isChatThread(currentReport);
+ const lastAccessedReportID = ReportUtils.findLastAccessedReport(allReports, false, undefined, false, false, reportMetadata, undefined, [], currentReport?.reportID)?.reportID;
+
if (lastAccessedReportID) {
const lastAccessedReportRoute = ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID ?? '-1');
Navigation.goBack(lastAccessedReportRoute);
} else {
- const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]);
- const chat = ReportUtils.getChatByParticipants([...participantAccountIDs, currentUserAccountID]);
- if (chat?.reportID) {
- // If it is not a chat thread we should call Navigation.goBack to pop the current route first before navigating to Concierge.
- if (!isChatThread) {
- Navigation.goBack();
- }
- Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chat?.reportID), CONST.NAVIGATION.TYPE.UP);
+ const isChatThread = ReportUtils.isChatThread(currentReport);
+
+ // If it is not a chat thread we should call Navigation.goBack to pop the current route first before navigating to Concierge.
+ if (!isChatThread) {
+ Navigation.goBack();
}
+
+ navigateToConciergeChat(false, () => true, CONST.NAVIGATION.TYPE.UP);
}
}
@@ -3230,6 +3211,8 @@ function completeOnboarding(
description: taskDescription ?? '',
}));
+ const hasOutstandingChildTask = tasksData.some((task) => !task.completedTaskReportAction);
+
const tasksForOptimisticData = tasksData.reduce((acc, {currentTask, taskCreatedAction, taskReportAction, taskDescription, completedTaskReportAction}) => {
acc.push(
{
@@ -3252,6 +3235,7 @@ function completeOnboarding(
managerID: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
isOptimisticReport: true,
+ managerID: currentUserAccountID,
},
},
{
@@ -3278,6 +3262,7 @@ function completeOnboarding(
value: {
stateNum: CONST.REPORT.STATE_NUM.APPROVED,
statusNum: CONST.REPORT.STATUS_NUM.APPROVED,
+ managerID: currentUserAccountID,
},
});
}
@@ -3362,6 +3347,7 @@ function completeOnboarding(
key: `${ONYXKEYS.COLLECTION.REPORT}${targetChatReportID}`,
value: {
lastMentionedTime: DateUtils.getDBTime(),
+ hasOutstandingChildTask,
},
},
{
@@ -3393,6 +3379,7 @@ function completeOnboarding(
lastMessageTranslationKey: '',
lastMessageText: '',
lastVisibleActionCreated: '',
+ hasOutstandingChildTask: false,
};
const {lastMessageText = '', lastMessageTranslationKey = ''} = ReportActionsUtils.getLastVisibleMessage(targetChatReportID);
if (lastMessageText || lastMessageTranslationKey) {
diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts
index 83e6716accb9..013ae698ed3f 100644
--- a/src/libs/actions/Task.ts
+++ b/src/libs/actions/Task.ts
@@ -132,12 +132,14 @@ function createTaskAndNavigate(
const currentTime = DateUtils.getDBTimeWithSkew();
const lastCommentText = ReportUtils.formatReportLastMessageText(ReportActionsUtils.getReportActionText(optimisticAddCommentReport.reportAction));
+ const parentReport = getReport(parentReportID);
const optimisticParentReport = {
lastVisibleActionCreated: optimisticAddCommentReport.reportAction.created,
lastMessageText: lastCommentText,
lastActorAccountID: currentUserAccountID,
lastReadTime: currentTime,
lastMessageTranslationKey: '',
+ hasOutstandingChildTask: assigneeAccountID === currentUserAccountID ? true : parentReport?.hasOutstandingChildTask,
};
// We're only setting onyx data for the task report here because it's possible for the parent report to not exist yet (if you're assigning a task to someone you haven't chatted with before)
@@ -272,6 +274,13 @@ function createTaskAndNavigate(
},
},
});
+ failureData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`,
+ value: {
+ hasOutstandingChildTask: parentReport?.hasOutstandingChildTask,
+ },
+ });
clearOutTaskInfo();
@@ -295,6 +304,29 @@ function createTaskAndNavigate(
Report.notifyNewAction(parentReportID, currentUserAccountID);
}
+/**
+ * @returns the object to update `report.hasOutstandingChildTask`
+ */
+function getOutstandingChildTask(taskReport: OnyxEntry) {
+ const parentReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReport?.parentReportID}`] ?? {};
+ return Object.values(parentReportActions).some((reportAction) => {
+ if (String(reportAction.childReportID) === String(taskReport?.reportID)) {
+ return false;
+ }
+
+ if (
+ reportAction.childType === CONST.REPORT.TYPE.TASK &&
+ reportAction?.childStateNum === CONST.REPORT.STATE_NUM.OPEN &&
+ reportAction?.childStatusNum === CONST.REPORT.STATUS_NUM.OPEN &&
+ ReportActionsUtils.getReportActionMessage(reportAction)?.isDeletedParentAction
+ ) {
+ return true;
+ }
+
+ return false;
+ });
+}
+
/**
* Complete a task
*/
@@ -302,7 +334,7 @@ function completeTask(taskReport: OnyxEntry) {
const taskReportID = taskReport?.reportID ?? '-1';
const message = `marked as complete`;
const completedTaskReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASK_COMPLETED, message);
-
+ const parentReport = getParentReport(taskReport);
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
@@ -353,6 +385,24 @@ function completeTask(taskReport: OnyxEntry) {
},
];
+ if (parentReport?.hasOutstandingChildTask) {
+ const hasOutstandingChildTask = getOutstandingChildTask(taskReport);
+ optimisticData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${taskReport?.parentReportID}`,
+ value: {
+ hasOutstandingChildTask,
+ },
+ });
+ failureData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${taskReport?.parentReportID}`,
+ value: {
+ hasOutstandingChildTask: parentReport?.hasOutstandingChildTask,
+ },
+ });
+ }
+
const parameters: CompleteTaskParams = {
taskReportID,
completedTaskReportActionID: completedTaskReportAction.reportActionID,
@@ -370,6 +420,8 @@ function reopenTask(taskReport: OnyxEntry) {
const taskReportID = taskReport?.reportID ?? '-1';
const message = `marked as incomplete`;
const reopenedTaskReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASK_REOPENED, message);
+ const parentReport = getParentReport(taskReport);
+ const hasOutstandingChildTask = taskReport?.managerID === currentUserAccountID ? true : parentReport?.hasOutstandingChildTask;
const optimisticData: OnyxUpdate[] = [
{
@@ -384,6 +436,13 @@ function reopenTask(taskReport: OnyxEntry) {
lastReadTime: reopenedTaskReportAction.created,
},
},
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${taskReport?.parentReportID}`,
+ value: {
+ hasOutstandingChildTask,
+ },
+ },
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReportID}`,
@@ -411,6 +470,13 @@ function reopenTask(taskReport: OnyxEntry) {
statusNum: CONST.REPORT.STATUS_NUM.APPROVED,
},
},
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${taskReport?.parentReportID}`,
+ value: {
+ hasOutstandingChildTask: taskReport?.hasOutstandingChildTask,
+ },
+ },
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReportID}`,
@@ -566,6 +632,39 @@ function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assi
},
];
+ if (currentUserAccountID === assigneeAccountID) {
+ const parentReport = getParentReport(report);
+ if (!isEmptyObject(parentReport)) {
+ optimisticData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`,
+ value: {hasOutstandingChildTask: true},
+ });
+ failureData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`,
+ value: {hasOutstandingChildTask: parentReport?.hasOutstandingChildTask},
+ });
+ }
+ }
+
+ if (report.managerID === currentUserAccountID) {
+ const hasOutstandingChildTask = getOutstandingChildTask(report);
+ const parentReport = getParentReport(report);
+ if (!isEmptyObject(parentReport)) {
+ optimisticData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`,
+ value: {hasOutstandingChildTask},
+ });
+ failureData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`,
+ value: {hasOutstandingChildTask: parentReport?.hasOutstandingChildTask},
+ });
+ }
+ }
+
// If we make a change to the assignee, we want to add a comment to the assignee's chat
// Check if the assignee actually changed
if (assigneeAccountID && assigneeAccountID !== report.managerID && assigneeAccountID !== ownerAccountID && assigneeChatReport) {
@@ -813,6 +912,13 @@ function getParentReport(report: OnyxEntry): OnyxEntry {
+ return allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
+}
+
/**
* Cancels a task by setting the report state to SUBMITTED and status to CLOSED
*/
@@ -847,6 +953,7 @@ function deleteTask(report: OnyxEntry) {
const optimisticReportActions = {
[parentReportAction?.reportActionID ?? '-1']: optimisticReportAction,
};
+ const hasOutstandingChildTask = getOutstandingChildTask(report);
const optimisticData: OnyxUpdate[] = [
{
@@ -865,6 +972,7 @@ function deleteTask(report: OnyxEntry) {
value: {
lastMessageText: ReportActionsUtils.getLastVisibleMessage(parentReport?.reportID ?? '-1', optimisticReportActions as OnyxTypes.ReportActions).lastMessageText ?? '',
lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(parentReport?.reportID ?? '-1', optimisticReportActions as OnyxTypes.ReportActions)?.created,
+ hasOutstandingChildTask,
},
},
{
@@ -927,6 +1035,13 @@ function deleteTask(report: OnyxEntry) {
statusNum: report.statusNum ?? '',
} as OnyxTypes.Report,
},
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`,
+ value: {
+ hasOutstandingChildTask: parentReport?.hasOutstandingChildTask,
+ },
+ },
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`,
diff --git a/src/pages/NewChatConfirmPage.tsx b/src/pages/NewChatConfirmPage.tsx
index 78e28cda332f..17e5708803cd 100644
--- a/src/pages/NewChatConfirmPage.tsx
+++ b/src/pages/NewChatConfirmPage.tsx
@@ -105,7 +105,7 @@ function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmP
}
const logins: string[] = (newGroupDraft.participants ?? []).map((participant) => participant.login);
- Report.navigateToAndOpenReport(logins, true, newGroupDraft.reportName ?? '', newGroupDraft.avatarUri ?? '', avatarFile, optimisticReportID.current, true);
+ Report.navigateToAndOpenReport(logins, true, undefined, newGroupDraft.reportName ?? '', newGroupDraft.avatarUri ?? '', avatarFile, optimisticReportID.current, true);
}, [newGroupDraft, avatarFile]);
const stashedLocalAvatarImage = newGroupDraft?.avatarUri;
diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx
index b1ef9fbfdd97..dcbad36d1eda 100644
--- a/src/pages/ReportDetailsPage.tsx
+++ b/src/pages/ReportDetailsPage.tsx
@@ -22,7 +22,6 @@ import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
-import usePaginatedReportActions from '@hooks/usePaginatedReportActions';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import type {ReportDetailsNavigatorParamList} from '@libs/Navigation/types';
@@ -80,10 +79,21 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const styles = useThemeStyles();
+
// The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here.
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || '-1'}`);
- const {reportActions} = usePaginatedReportActions(report.reportID);
+ const [sortedAllReportActions = []] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID ?? '-1'}`, {
+ canEvict: false,
+ selector: (allReportActions: OnyxEntry) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, true),
+ });
+
+ const reportActions = useMemo(() => {
+ if (!sortedAllReportActions.length) {
+ return [];
+ }
+ return ReportActionsUtils.getContinuousReportActionChain(sortedAllReportActions);
+ }, [sortedAllReportActions]);
const transactionThreadReportID = useMemo(
() => ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions ?? [], isOffline),
diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx
index 3076aeb87bd1..3c2ae7bbc6e6 100644
--- a/src/pages/home/ReportScreen.tsx
+++ b/src/pages/home/ReportScreen.tsx
@@ -26,11 +26,11 @@ import useIsReportOpenInRHP from '@hooks/useIsReportOpenInRHP';
import useLastAccessedReportID from '@hooks/useLastAccessedReportID';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
-import usePaginatedReportActions from '@hooks/usePaginatedReportActions';
import usePrevious from '@hooks/usePrevious';
import useThemeStyles from '@hooks/useThemeStyles';
import useViewportOffsetTop from '@hooks/useViewportOffsetTop';
import useWindowDimensions from '@hooks/useWindowDimensions';
+import {getCurrentUserAccountID} from '@libs/actions/Report';
import Timing from '@libs/actions/Timing';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
@@ -68,6 +68,9 @@ type ReportScreenOnyxProps = {
/** The policies which the user has access to */
policies: OnyxCollection;
+ /** An array containing all report actions related to this report, sorted based on a date criterion */
+ sortedAllReportActions: OnyxTypes.ReportAction[];
+
/** Additional report details */
reportNameValuePairs: OnyxEntry;
@@ -116,6 +119,7 @@ function ReportScreen({
betas = [],
route,
reportNameValuePairs,
+ sortedAllReportActions,
reportMetadata = {
isLoadingInitialReportActions: true,
isLoadingOlderReportActions: false,
@@ -279,14 +283,12 @@ function ReportScreen({
const prevReport = usePrevious(report);
const prevUserLeavingStatus = usePrevious(userLeavingStatus);
const [isLinkingToMessage, setIsLinkingToMessage] = useState(!!reportActionIDFromRoute);
-
- const [currentUserAccountID = -1] = useOnyx(ONYXKEYS.SESSION, {selector: (value) => value?.accountID});
- const {reportActions, linkedAction} = usePaginatedReportActions(report.reportID, reportActionIDFromRoute);
- const isLinkedActionDeleted = useMemo(() => !!linkedAction && !ReportActionsUtils.shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID), [linkedAction]);
- const isLinkedActionInaccessibleWhisper = useMemo(
- () => !!linkedAction && ReportActionsUtils.isWhisperAction(linkedAction) && !(linkedAction?.whisperedToAccountIDs ?? []).includes(currentUserAccountID),
- [currentUserAccountID, linkedAction],
- );
+ const reportActions = useMemo(() => {
+ if (!sortedAllReportActions.length) {
+ return [];
+ }
+ return ReportActionsUtils.getContinuousReportActionChain(sortedAllReportActions, reportActionIDFromRoute);
+ }, [reportActionIDFromRoute, sortedAllReportActions]);
// Define here because reportActions are recalculated before mount, allowing data to display faster than useEffect can trigger.
// If we have cached reportActions, they will be shown immediately.
@@ -311,6 +313,10 @@ function ReportScreen({
const screenWrapperStyle: ViewStyle[] = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}];
const isEmptyChat = useMemo(() => ReportUtils.isEmptyReport(report), [report]);
const isOptimisticDelete = report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED;
+ const isLinkedMessageAvailable = useMemo(
+ (): boolean => sortedAllReportActions.findIndex((obj) => String(obj.reportActionID) === String(reportActionIDFromRoute)) > -1,
+ [sortedAllReportActions, reportActionIDFromRoute],
+ );
// If there's a non-404 error for the report we should show it instead of blocking the screen
const hasHelpfulErrors = Object.keys(report?.errorFields ?? {}).some((key) => key !== 'notFound');
@@ -402,12 +408,12 @@ function ReportScreen({
const isLoading = isLoadingApp ?? (!reportIDFromRoute || (!isSidebarLoaded && !isReportOpenInRHP) || PersonalDetailsUtils.isPersonalDetailsEmpty());
const shouldShowSkeleton =
- !linkedAction &&
+ !isLinkedMessageAvailable &&
(isLinkingToMessage ||
!isCurrentReportLoadedFromOnyx ||
(reportActions.length === 0 && !!reportMetadata?.isLoadingInitialReportActions) ||
isLoading ||
- (!!reportActionIDFromRoute && !!reportMetadata?.isLoadingInitialReportActions));
+ (!!reportActionIDFromRoute && reportMetadata?.isLoadingInitialReportActions));
const shouldShowReportActionList = isCurrentReportLoadedFromOnyx && !isLoading;
const currentReportIDFormRoute = route.params?.reportID;
@@ -672,16 +678,28 @@ function ReportScreen({
fetchReport();
}, [fetchReport]);
+ const {isLinkedReportActionDeleted, isInaccessibleWhisper} = useMemo(() => {
+ const currentUserAccountID = getCurrentUserAccountID();
+ if (!reportActionIDFromRoute || !sortedAllReportActions) {
+ return {isLinkedReportActionDeleted: false, isInaccessibleWhisper: false};
+ }
+ const action = sortedAllReportActions.find((item) => item.reportActionID === reportActionIDFromRoute);
+ return {
+ isLinkedReportActionDeleted: action && !ReportActionsUtils.shouldReportActionBeVisible(action, action.reportActionID),
+ isInaccessibleWhisper: action && ReportActionsUtils.isWhisperAction(action) && !(action?.whisperedToAccountIDs ?? []).includes(currentUserAccountID),
+ };
+ }, [reportActionIDFromRoute, sortedAllReportActions]);
+
// If user redirects to an inaccessible whisper via a deeplink, on a report they have access to,
// then we set reportActionID as empty string, so we display them the report and not the "Not found page".
useEffect(() => {
- if (!isLinkedActionInaccessibleWhisper) {
+ if (!isInaccessibleWhisper) {
return;
}
Navigation.isNavigationReady().then(() => {
Navigation.setParams({reportActionID: ''});
});
- }, [isLinkedActionInaccessibleWhisper]);
+ }, [isInaccessibleWhisper]);
useEffect(() => {
if (!!report.lastReadTime || !ReportUtils.isTaskReport(report)) {
@@ -691,7 +709,7 @@ function ReportScreen({
Report.readNewestAction(report.reportID);
}, [report]);
- if ((!isLinkedActionInaccessibleWhisper && isLinkedActionDeleted) ?? (!shouldShowSkeleton && reportActionIDFromRoute && reportActions?.length === 0 && !isLinkingToMessage)) {
+ if ((!isInaccessibleWhisper && isLinkedReportActionDeleted) ?? (!shouldShowSkeleton && reportActionIDFromRoute && reportActions?.length === 0 && !isLinkingToMessage)) {
return (
`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getReportID(route)}`,
+ canEvict: false,
+ selector: (allReportActions: OnyxEntry) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, true),
+ },
reportNameValuePairs: {
key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${getReportID(route)}`,
allowStaleData: true,
@@ -833,6 +856,7 @@ export default withCurrentReportID(
ReportScreen,
(prevProps, nextProps) =>
prevProps.isSidebarLoaded === nextProps.isSidebarLoaded &&
+ lodashIsEqual(prevProps.sortedAllReportActions, nextProps.sortedAllReportActions) &&
lodashIsEqual(prevProps.reportMetadata, nextProps.reportMetadata) &&
lodashIsEqual(prevProps.betas, nextProps.betas) &&
lodashIsEqual(prevProps.policies, nextProps.policies) &&
diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx
index 29bd29de7b83..242bb5885d1d 100644
--- a/src/pages/workspace/WorkspaceInitialPage.tsx
+++ b/src/pages/workspace/WorkspaceInitialPage.tsx
@@ -1,4 +1,4 @@
-import {useFocusEffect, useNavigationState} from '@react-navigation/native';
+import {useNavigationState} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
@@ -87,7 +87,7 @@ function dismissError(policyID: string, pendingAction: PendingAction | undefined
}
}
-function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAccount, policyCategories, route}: WorkspaceInitialPageProps) {
+function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAccount, policyCategories}: WorkspaceInitialPageProps) {
const styles = useThemeStyles();
const policy = policyDraft?.id ? policyDraft : policyProp;
const [isCurrencyModalOpen, setIsCurrencyModalOpen] = useState(false);
@@ -135,18 +135,6 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc
setIsCurrencyModalOpen(false);
}, [policy?.outputCurrency, isCurrencyModalOpen]);
- const fetchPolicyData = useCallback(() => {
- Policy.openPolicyInitialPage(route.params.policyID);
- }, [route.params.policyID]);
-
- useNetwork({onReconnect: fetchPolicyData});
-
- useFocusEffect(
- useCallback(() => {
- fetchPolicyData();
- }, [fetchPolicyData]),
- );
-
/** Call update workspace currency and hide the modal */
const confirmCurrencyChangeAndHideModal = useCallback(() => {
Policy.updateGeneralSettings(policyID, policyName, CONST.CURRENCY.USD);
diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx
index 6f2097688b5c..6a86a83b5d11 100644
--- a/src/pages/workspace/WorkspaceProfilePage.tsx
+++ b/src/pages/workspace/WorkspaceProfilePage.tsx
@@ -1,5 +1,3 @@
-import {useFocusEffect} from '@react-navigation/native';
-import type {StackScreenProps} from '@react-navigation/stack';
import {ExpensiMark} from 'expensify-common';
import React, {useCallback, useState} from 'react';
import type {ImageStyle, StyleProp} from 'react-native';
@@ -17,14 +15,12 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback';
import Section from '@components/Section';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useLocalize from '@hooks/useLocalize';
-import useNetwork from '@hooks/useNetwork';
import usePermissions from '@hooks/usePermissions';
import useThemeIllustrations from '@hooks/useThemeIllustrations';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
-import type {FullScreenNavigatorParamList} from '@libs/Navigation/types';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import StringUtils from '@libs/StringUtils';
@@ -33,23 +29,22 @@ import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import type SCREENS from '@src/SCREENS';
import type * as OnyxTypes from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import withPolicy from './withPolicy';
import type {WithPolicyProps} from './withPolicy';
import WorkspacePageWithSections from './WorkspacePageWithSections';
-type WorkspaceProfilePageOnyxProps = {
+type WorkSpaceProfilePageOnyxProps = {
/** Constant, list of available currencies */
currencyList: OnyxEntry;
};
-type WorkspaceProfilePageProps = WithPolicyProps & WorkspaceProfilePageOnyxProps & StackScreenProps;
+type WorkSpaceProfilePageProps = WithPolicyProps & WorkSpaceProfilePageOnyxProps;
const parser = new ExpensiMark();
-function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkspaceProfilePageProps) {
+function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfilePageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isSmallScreenWidth} = useWindowDimensions();
@@ -87,20 +82,6 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkspaceProfi
const imageStyle: StyleProp = isSmallScreenWidth ? [styles.mhv12, styles.mhn5, styles.mbn5] : [styles.mhv8, styles.mhn8, styles.mbn5];
const shouldShowAddress = !readOnly || formattedAddress;
- const fetchPolicyData = useCallback(() => {
- Policy.openPolicyProfilePage(route.params.policyID);
- }, [route.params.policyID]);
-
- useNetwork({onReconnect: fetchPolicyData});
-
- // We have the same focus effect in the WorkspaceInitialPage, this way we can get the policy data in narrow
- // as well as in the wide layout when looking at policy settings.
- useFocusEffect(
- useCallback(() => {
- fetchPolicyData();
- }, [fetchPolicyData]),
- );
-
const DefaultAvatar = useCallback(
() => (
({
+ withOnyx({
currencyList: {key: ONYXKEYS.CURRENCY_LIST},
})(WorkspaceProfilePage),
);
diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx
index 4a908c0928ab..6300d3fcf280 100644
--- a/src/pages/workspace/withPolicy.tsx
+++ b/src/pages/workspace/withPolicy.tsx
@@ -16,7 +16,6 @@ type PolicyRoute = RouteProp<
NavigatorsParamList,
| typeof SCREENS.REIMBURSEMENT_ACCOUNT_ROOT
| typeof SCREENS.WORKSPACE.INITIAL
- | typeof SCREENS.WORKSPACE.PROFILE
| typeof SCREENS.WORKSPACE.BILLS
| typeof SCREENS.WORKSPACE.MORE_FEATURES
| typeof SCREENS.WORKSPACE.MEMBERS
diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts
index 9b0b35a6da7c..b5d0a0a15d13 100644
--- a/src/types/onyx/Report.ts
+++ b/src/types/onyx/Report.ts
@@ -77,6 +77,9 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback<
/** Whether the report has a child that is an outstanding expense that is awaiting action from the current user */
hasOutstandingChildRequest?: boolean;
+ /** Whether the report has a child task that is awaiting action from the current user */
+ hasOutstandingChildTask?: boolean;
+
/** List of icons for report participants */
icons?: OnyxCommon.Icon[];
diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx
index f84c75823753..b5990ee5d002 100644
--- a/tests/ui/UnreadIndicatorsTest.tsx
+++ b/tests/ui/UnreadIndicatorsTest.tsx
@@ -200,104 +200,81 @@ let reportAction9CreatedDate: string;
/**
* Sets up a test with a logged in user that has one unread chat from another user. Returns the test instance.
*/
-async function signInAndGetAppWithUnreadChat() {
+function signInAndGetAppWithUnreadChat(): Promise {
// Render the App and sign in as a test user.
render();
- await waitForBatchedUpdatesWithAct();
- await waitForBatchedUpdatesWithAct();
-
- const hintText = Localize.translateLocal('loginForm.loginForm');
- const loginForm = screen.queryAllByLabelText(hintText);
- expect(loginForm).toHaveLength(1);
-
- await act(async () => {
- await TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A');
- });
- await waitForBatchedUpdatesWithAct();
-
- User.subscribeToUserEvents();
- await waitForBatchedUpdates();
-
- const TEN_MINUTES_AGO = subMinutes(new Date(), 10);
- reportAction3CreatedDate = format(addSeconds(TEN_MINUTES_AGO, 30), CONST.DATE.FNS_DB_FORMAT_STRING);
- reportAction9CreatedDate = format(addSeconds(TEN_MINUTES_AGO, 90), CONST.DATE.FNS_DB_FORMAT_STRING);
-
- // Simulate setting an unread report and personal details
- await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, {
- reportID: REPORT_ID,
- reportName: CONST.REPORT.DEFAULT_REPORT_NAME,
- lastReadTime: reportAction3CreatedDate,
- lastVisibleActionCreated: reportAction9CreatedDate,
- lastMessageText: 'Test',
- participants: {[USER_B_ACCOUNT_ID]: {hidden: false}},
- lastActorAccountID: USER_B_ACCOUNT_ID,
- type: CONST.REPORT.TYPE.CHAT,
- });
- const createdReportActionID = NumberUtils.rand64().toString();
- await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, {
- [createdReportActionID]: {
- actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
- automatic: false,
- created: format(TEN_MINUTES_AGO, CONST.DATE.FNS_DB_FORMAT_STRING),
- reportActionID: createdReportActionID,
- message: [
- {
- style: 'strong',
- text: '__FAKE__',
- type: 'TEXT',
- },
- {
- style: 'normal',
- text: 'created this report',
- type: 'TEXT',
+ return waitForBatchedUpdatesWithAct()
+ .then(async () => {
+ await waitForBatchedUpdatesWithAct();
+ const hintText = Localize.translateLocal('loginForm.loginForm');
+ const loginForm = screen.queryAllByLabelText(hintText);
+ expect(loginForm).toHaveLength(1);
+
+ await act(async () => {
+ await TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A');
+ });
+ return waitForBatchedUpdatesWithAct();
+ })
+ .then(() => {
+ User.subscribeToUserEvents();
+ return waitForBatchedUpdates();
+ })
+ .then(async () => {
+ const TEN_MINUTES_AGO = subMinutes(new Date(), 10);
+ reportAction3CreatedDate = format(addSeconds(TEN_MINUTES_AGO, 30), CONST.DATE.FNS_DB_FORMAT_STRING);
+ reportAction9CreatedDate = format(addSeconds(TEN_MINUTES_AGO, 90), CONST.DATE.FNS_DB_FORMAT_STRING);
+
+ // Simulate setting an unread report and personal details
+ await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, {
+ reportID: REPORT_ID,
+ reportName: CONST.REPORT.DEFAULT_REPORT_NAME,
+ lastReadTime: reportAction3CreatedDate,
+ lastVisibleActionCreated: reportAction9CreatedDate,
+ lastMessageText: 'Test',
+ participants: {[USER_B_ACCOUNT_ID]: {hidden: false}},
+ lastActorAccountID: USER_B_ACCOUNT_ID,
+ type: CONST.REPORT.TYPE.CHAT,
+ });
+ const createdReportActionID = NumberUtils.rand64().toString();
+ await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, {
+ [createdReportActionID]: {
+ actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
+ automatic: false,
+ created: format(TEN_MINUTES_AGO, CONST.DATE.FNS_DB_FORMAT_STRING),
+ reportActionID: createdReportActionID,
+ message: [
+ {
+ style: 'strong',
+ text: '__FAKE__',
+ type: 'TEXT',
+ },
+ {
+ style: 'normal',
+ text: 'created this report',
+ type: 'TEXT',
+ },
+ ],
},
- ],
- },
- 1: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 10), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '1', createdReportActionID),
- 2: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 20), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '2', '1'),
- 3: TestHelper.buildTestReportComment(reportAction3CreatedDate, USER_B_ACCOUNT_ID, '3', '2'),
- 4: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 40), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '4', '3'),
- 5: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 50), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '5', '4'),
- 6: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 60), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '6', '5'),
- 7: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 70), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '7', '6'),
- 8: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 80), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '8', '7'),
- 9: TestHelper.buildTestReportComment(reportAction9CreatedDate, USER_B_ACCOUNT_ID, '9', '8'),
- });
- await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
- [USER_B_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_B_EMAIL, USER_B_ACCOUNT_ID, 'B'),
- });
-
- // We manually setting the sidebar as loaded since the onLayout event does not fire in tests
- AppActions.setSidebarLoaded();
-
- await waitForBatchedUpdatesWithAct();
-}
-
-let lastComment = 'Current User Comment 1';
-async function addComment() {
- const num = Number.parseInt(lastComment.slice(-1), 10);
- lastComment = `${lastComment.slice(0, -1)}${num + 1}`;
- const comment = lastComment;
- const reportActionsBefore = (await TestHelper.onyxGet(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`)) as Record;
- Report.addComment(REPORT_ID, comment);
- const reportActionsAfter = (await TestHelper.onyxGet(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`)) as Record;
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const newReportActionID = Object.keys(reportActionsAfter).find((reportActionID) => !reportActionsBefore[reportActionID])!;
- await act(() =>
- Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, {
- [newReportActionID]: {
- previousReportActionID: '9',
- },
- }),
- );
- await waitForBatchedUpdatesWithAct();
-
- // Verify the comment is visible (it will appear twice, once in the LHN and once on the report screen)
- expect(screen.getAllByText(comment)[0]).toBeOnTheScreen();
+ 1: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 10), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '1', createdReportActionID),
+ 2: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 20), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '2', '1'),
+ 3: TestHelper.buildTestReportComment(reportAction3CreatedDate, USER_B_ACCOUNT_ID, '3', '2'),
+ 4: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 40), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '4', '3'),
+ 5: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 50), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '5', '4'),
+ 6: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 60), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '6', '5'),
+ 7: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 70), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '7', '6'),
+ 8: TestHelper.buildTestReportComment(format(addSeconds(TEN_MINUTES_AGO, 80), CONST.DATE.FNS_DB_FORMAT_STRING), USER_B_ACCOUNT_ID, '8', '7'),
+ 9: TestHelper.buildTestReportComment(reportAction9CreatedDate, USER_B_ACCOUNT_ID, '9', '8'),
+ });
+ await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
+ [USER_B_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_B_EMAIL, USER_B_ACCOUNT_ID, 'B'),
+ });
+
+ // We manually setting the sidebar as loaded since the onLayout event does not fire in tests
+ AppActions.setSidebarLoaded();
+ return waitForBatchedUpdatesWithAct();
+ });
}
-const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
-
describe('Unread Indicators', () => {
afterEach(() => {
jest.clearAllMocks();
@@ -342,6 +319,7 @@ describe('Unread Indicators', () => {
expect(reportComments).toHaveLength(9);
// Since the last read timestamp is the timestamp of action 3 we should have an unread indicator above the next "unread" action which will
// have actionID of 4
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(1);
const reportActionID = unreadIndicator[0]?.props?.['data-action-id'];
@@ -357,6 +335,7 @@ describe('Unread Indicators', () => {
.then(async () => {
await act(() => transitionEndCB?.());
// Verify the unread indicator is present
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(1);
})
@@ -379,6 +358,7 @@ describe('Unread Indicators', () => {
})
.then(() => {
// Verify the unread indicator is not present
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(0);
// Tap on the chat again
@@ -386,6 +366,7 @@ describe('Unread Indicators', () => {
})
.then(() => {
// Verify the unread indicator is not present
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(0);
expect(areYouOnChatListScreen()).toBe(false);
@@ -495,6 +476,7 @@ describe('Unread Indicators', () => {
})
.then(() => {
// Verify the indicator appears above the last action
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(1);
const reportActionID = unreadIndicator[0]?.props?.['data-action-id'];
@@ -529,6 +511,7 @@ describe('Unread Indicators', () => {
return navigateToSidebarOption(0);
})
.then(() => {
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(0);
@@ -537,23 +520,30 @@ describe('Unread Indicators', () => {
return waitFor(() => expect(isNewMessagesBadgeVisible()).toBe(false));
}));
- it('Keep showing the new line indicator when a new message is created by the current user', async () => {
- await signInAndGetAppWithUnreadChat();
-
- // Verify we are on the LHN and that the chat shows as unread in the LHN
- expect(areYouOnChatListScreen()).toBe(true);
+ it('Keep showing the new line indicator when a new message is created by the current user', () =>
+ signInAndGetAppWithUnreadChat()
+ .then(() => {
+ // Verify we are on the LHN and that the chat shows as unread in the LHN
+ expect(areYouOnChatListScreen()).toBe(true);
- // Navigate to the report and verify the indicator is present
- await navigateToSidebarOption(0);
- await act(() => transitionEndCB?.());
- let unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
- expect(unreadIndicator).toHaveLength(1);
+ // Navigate to the report and verify the indicator is present
+ return navigateToSidebarOption(0);
+ })
+ .then(async () => {
+ await act(() => transitionEndCB?.());
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
+ const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
+ expect(unreadIndicator).toHaveLength(1);
- // Leave a comment as the current user and verify the indicator is not removed
- await addComment();
- unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
- expect(unreadIndicator).toHaveLength(1);
- });
+ // Leave a comment as the current user and verify the indicator is removed
+ Report.addComment(REPORT_ID, 'Current User Comment 1');
+ return waitForBatchedUpdates();
+ })
+ .then(() => {
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
+ const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
+ expect(unreadIndicator).toHaveLength(1);
+ }));
xit('Keeps the new line indicator when the user moves the App to the background', () =>
signInAndGetAppWithUnreadChat()
@@ -565,6 +555,7 @@ describe('Unread Indicators', () => {
return navigateToSidebarOption(0);
})
.then(() => {
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(1);
@@ -573,6 +564,7 @@ describe('Unread Indicators', () => {
})
.then(() => navigateToSidebarOption(0))
.then(() => {
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(0);
@@ -581,6 +573,7 @@ describe('Unread Indicators', () => {
return waitForBatchedUpdates();
})
.then(() => {
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
let unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(1);
@@ -604,10 +597,11 @@ describe('Unread Indicators', () => {
signInAndGetAppWithUnreadChat()
// Navigate to the chat and simulate leaving a comment from the current user
.then(() => navigateToSidebarOption(0))
- .then(() =>
+ .then(() => {
// Leave a comment as the current user
- addComment(),
- )
+ Report.addComment(REPORT_ID, 'Current User Comment 1');
+ return waitForBatchedUpdates();
+ })
.then(() => {
// Simulate the response from the server so that the comment can be deleted in this test
lastReportAction = reportActions ? CollectionUtils.lastItem(reportActions) : undefined;
@@ -625,7 +619,7 @@ describe('Unread Indicators', () => {
expect(alternateText).toHaveLength(1);
// This message is visible on the sidebar and the report screen, so there are two occurrences.
- expect(screen.getAllByText(lastComment)[0]).toBeOnTheScreen();
+ expect(screen.getAllByText('Current User Comment 1')[0]).toBeOnTheScreen();
if (lastReportAction) {
Report.deleteReportComment(REPORT_ID, lastReportAction);
diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts
index dffb2b4e312a..9ca0969abc6a 100644
--- a/tests/utils/TestHelper.ts
+++ b/tests/utils/TestHelper.ts
@@ -1,6 +1,5 @@
import {Str} from 'expensify-common';
import Onyx from 'react-native-onyx';
-import type {ConnectOptions, OnyxKey} from 'react-native-onyx';
import CONST from '@src/CONST';
import * as Session from '@src/libs/actions/Session';
import HttpUtils from '@src/libs/HttpUtils';
@@ -248,33 +247,5 @@ const createAddListenerMock = () => {
return {triggerTransitionEnd, addListener};
};
-/**
- * Get an Onyx value. Only for use in tests for now.
- */
-async function onyxGet(key: OnyxKey): Promise>['callback']>[0]> {
- return new Promise((resolve) => {
- // eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs
- // @ts-expect-error This does not need more strict type checking as it's only for tests
- const connectionID = Onyx.connect({
- key,
- callback: (value) => {
- Onyx.disconnect(connectionID);
- resolve(value);
- },
- waitForCollectionCallback: true,
- });
- });
-}
-
export type {MockFetch, FormData};
-export {
- assertFormDataMatchesObject,
- buildPersonalDetails,
- buildTestReportComment,
- createAddListenerMock,
- getGlobalFetchMock,
- setPersonalDetails,
- signInWithTestUser,
- signOutTestUser,
- onyxGet,
-};
+export {assertFormDataMatchesObject, buildPersonalDetails, buildTestReportComment, createAddListenerMock, getGlobalFetchMock, setPersonalDetails, signInWithTestUser, signOutTestUser};