diff --git a/android/app/build.gradle b/android/app/build.gradle
index 4bd11d4e5a88..e48845c9ebbf 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -156,8 +156,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001021103
- versionName "1.2.11-3"
+ versionCode 1001021202
+ versionName "1.2.12-2"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index af004baa6d89..f35803fe8682 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.2.11
+ 1.2.12
CFBundleSignature
????
CFBundleURLTypes
@@ -30,7 +30,7 @@
CFBundleVersion
- 1.2.11.3
+ 1.2.12.2
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 2abe543d2735..a766da190a7f 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.2.11
+ 1.2.12
CFBundleSignature
????
CFBundleVersion
- 1.2.11.3
+ 1.2.12.2
diff --git a/package-lock.json b/package-lock.json
index be10d925d4ea..6d1d8edb7076 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.2.11-3",
+ "version": "1.2.12-2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.2.11-3",
+ "version": "1.2.12-2",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 8238f27f47f5..e9a6c18b1231 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.2.11-3",
+ "version": "1.2.12-2",
"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.",
diff --git a/src/CONST.js b/src/CONST.js
index f95292f39ce4..59403775effa 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -88,6 +88,10 @@ const CONST = {
},
REGEX: {
US_ACCOUNT_NUMBER: /^[0-9]{4,17}$/,
+
+ // If the account number length is from 4 to 13 digits, we show the last 4 digits and hide the rest with X
+ // If the length is longer than 13 digits, we show the first 6 and last 4 digits, hiding the rest with X
+ MASKED_US_ACCOUNT_NUMBER: /^[X]{0,9}[0-9]{4}$|^[0-9]{6}[X]{4,7}[0-9]{4}$/,
SWIFT_BIC: /^[A-Za-z0-9]{8,11}$/,
},
VERIFICATION_MAX_ATTEMPTS: 7,
@@ -801,6 +805,7 @@ const CONST = {
},
BRICK_ROAD_INDICATOR_STATUS: {
ERROR: 'error',
+ INFO: 'info',
},
REPORT_DETAILS_MENU_ITEM: {
MEMBERS: 'member',
diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index 70327599ca22..0b1f45006fc2 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -24,9 +24,6 @@ export default {
// Stores current date
CURRENT_DATE: 'currentDate',
- // Currently viewed reportID
- CURRENTLY_VIEWED_REPORTID: 'currentlyViewedReportID',
-
// Credentials to authenticate the user
CREDENTIALS: 'credentials',
@@ -161,9 +158,6 @@ export default {
// Is policy data loading?
IS_LOADING_POLICY_DATA: 'isLoadingPolicyData',
- // Are we loading the create policy room command
- IS_LOADING_CREATE_POLICY_ROOM: 'isLoadingCratePolicyRoom',
-
// Is Keyboard shortcuts modal open?
IS_SHORTCUTS_MODAL_OPEN: 'isShortcutsModalOpen',
diff --git a/src/components/BigNumberPad.js b/src/components/BigNumberPad.js
index 867b946f25bc..56dc37d5a440 100644
--- a/src/components/BigNumberPad.js
+++ b/src/components/BigNumberPad.js
@@ -78,7 +78,6 @@ class BigNumberPad extends React.Component {
ControlSelection.unblock();
this.props.longPressHandlerStateChanged(false);
}}
- textSelectable={false}
/>
);
})}
diff --git a/src/components/BlockingViews/FullPageNotFoundView.js b/src/components/BlockingViews/FullPageNotFoundView.js
index 6502852afd2a..960428027e14 100644
--- a/src/components/BlockingViews/FullPageNotFoundView.js
+++ b/src/components/BlockingViews/FullPageNotFoundView.js
@@ -55,7 +55,7 @@ const FullPageNotFoundView = (props) => {
onBackButtonPress={props.onBackButtonPress}
onCloseButtonPress={() => Navigation.dismissModal()}
/>
-
+
{
const titleTextStyle = StyleUtils.combineStyles([
styles.popoverMenuText,
styles.ml3,
+ (props.shouldShowBasicTitle ? undefined : styles.textStrong),
(props.interactive && props.disabled ? styles.disabledText : undefined),
], props.style);
- const descriptionTextStyle = StyleUtils.combineStyles([styles.textLabelSupporting, styles.ml3, styles.mt1, styles.breakAll], props.style);
+ const descriptionTextStyle = StyleUtils.combineStyles([styles.textLabelSupporting, styles.ml3, styles.breakAll], props.style);
return (
{
/>
)}
-
-
- {props.title}
-
- {Boolean(props.description) && (
+
+ {Boolean(props.description) && props.shouldShowDescriptionOnTop && (
+
+ {props.description}
+
+ )}
+ {Boolean(props.title) && (
+
+ {props.title}
+
+ )}
+ {Boolean(props.description) && !props.shouldShowDescriptionOnTop && (
(
+
+);
+
+MenuItemWithTopDescription.propTypes = propTypes;
+MenuItemWithTopDescription.displayName = 'MenuItemWithTopDescription';
+
+export default MenuItemWithTopDescription;
diff --git a/src/components/ReportActionItem/IOUAction.js b/src/components/ReportActionItem/IOUAction.js
index 1baa2305b378..83730269a909 100644
--- a/src/components/ReportActionItem/IOUAction.js
+++ b/src/components/ReportActionItem/IOUAction.js
@@ -14,7 +14,7 @@ const propTypes = {
action: PropTypes.shape(reportActionPropTypes).isRequired,
/** The associated chatReport */
- chatReportID: PropTypes.number.isRequired,
+ chatReportID: PropTypes.string.isRequired,
/** Is this IOUACTION the most recent? */
isMostRecentIOUReportAction: PropTypes.bool.isRequired,
@@ -47,7 +47,7 @@ const IOUAction = (props) => {
{((props.isMostRecentIOUReportAction && Boolean(props.action.originalMessage.IOUReportID))
|| (props.action.originalMessage.type === 'pay')) && (
{
// Determines the number of content items based on container height
- const possibleVisibleContentItems = Math.floor(props.containerHeight / CONST.CHAT_SKELETON_VIEW.AVERAGE_ROW_HEIGHT);
+ const possibleVisibleContentItems = Math.ceil(props.containerHeight / CONST.CHAT_SKELETON_VIEW.AVERAGE_ROW_HEIGHT);
const skeletonViewLines = [];
for (let index = 0; index < possibleVisibleContentItems; index++) {
const iconIndex = (index + 1) % 4;
diff --git a/src/components/menuItemPropTypes.js b/src/components/menuItemPropTypes.js
index 65f0dbf48d03..7f38175d564e 100644
--- a/src/components/menuItemPropTypes.js
+++ b/src/components/menuItemPropTypes.js
@@ -34,6 +34,12 @@ const propTypes = {
/** Should we make this selectable with a checkbox */
shouldShowSelectedState: PropTypes.bool,
+ /** Should the title show with normal font weight (not bold) */
+ shouldShowBasicTitle: PropTypes.bool,
+
+ /** Should the description be shown above the title (instead of the other way around) */
+ shouldShowDescriptionOnTop: PropTypes.bool,
+
/** Whether this item is selected */
isSelected: PropTypes.bool,
@@ -71,7 +77,7 @@ const propTypes = {
fallbackIcon: PropTypes.func,
/** The type of brick road indicator to show. */
- brickRoadIndicator: PropTypes.oneOf([CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, '']),
+ brickRoadIndicator: PropTypes.oneOf([CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, CONST.BRICK_ROAD_INDICATOR_STATUS.INFO, '']),
};
export default propTypes;
diff --git a/src/languages/en.js b/src/languages/en.js
index eea9ccc81f65..844f1b090c81 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -992,7 +992,6 @@ export default {
renamedRoomAction: ({oldName, newName}) => ` renamed this room from ${oldName} to ${newName}`,
social: 'social',
selectAWorkspace: 'Select a workspace',
- growlMessageOnError: 'Unable to create policy room, please check your connection and try again.',
growlMessageOnRenameError: 'Unable to rename policy room, please check your connection and try again.',
visibilityOptions: {
restricted: 'Restricted',
diff --git a/src/languages/es.js b/src/languages/es.js
index 394010a83425..66d8ceb364c1 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -994,7 +994,6 @@ export default {
renamedRoomAction: ({oldName, newName}) => ` cambió el nombre de la sala de ${oldName} a ${newName}`,
social: 'social',
selectAWorkspace: 'Seleccionar un espacio de trabajo',
- growlMessageOnError: 'No se pudo crear el espacio de trabajo, por favor comprueba tu conexión e inténtalo de nuevo.',
growlMessageOnRenameError: 'No se pudo cambiar el nomdre del espacio de trabajo, por favor comprueba tu conexión e inténtalo de nuevo.',
visibilityOptions: {
restricted: 'Restringida',
diff --git a/src/libs/Navigation/AppNavigator/MainDrawerNavigator.js b/src/libs/Navigation/AppNavigator/MainDrawerNavigator.js
index 5df2edc2178d..66b7ef2e7372 100644
--- a/src/libs/Navigation/AppNavigator/MainDrawerNavigator.js
+++ b/src/libs/Navigation/AppNavigator/MainDrawerNavigator.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, {Component} from 'react';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import {withOnyx} from 'react-native-onyx';
@@ -54,31 +54,55 @@ const getInitialReportScreenParams = (reports, ignoreDefaultRooms, policies) =>
return {reportID: String(reportID)};
};
-const MainDrawerNavigator = (props) => {
- const initialParams = getInitialReportScreenParams(props.reports, !Permissions.canUseDefaultRooms(props.betas), props.policies);
+class MainDrawerNavigator extends Component {
+ constructor(props) {
+ super(props);
+ this.initialParams = getInitialReportScreenParams(props.reports, !Permissions.canUseDefaultRooms(props.betas), props.policies);
+ }
+
+ shouldComponentUpdate(nextProps) {
+ const initialNextParams = getInitialReportScreenParams(nextProps.reports, !Permissions.canUseDefaultRooms(nextProps.betas), nextProps.policies);
+ if (this.initialParams.reportID === initialNextParams.reportID) {
+ return false;
+ }
- // Wait until reports are fetched and there is a reportID in initialParams
- if (!initialParams.reportID) {
- return ;
+ this.initialParams = initialNextParams;
+ return true;
}
- // After the app initializes and reports are available the home navigation is mounted
- // This way routing information is updated (if needed) based on the initial report ID resolved.
- // This is usually needed after login/create account and re-launches
- return (
- }
- screens={[
- {
- name: SCREENS.REPORT,
- component: ReportScreen,
- initialParams,
- },
- ]}
- isMainScreen
- />
- );
-};
+ render() {
+ // Wait until reports are fetched and there is a reportID in initialParams
+ if (!this.initialParams.reportID) {
+ return ;
+ }
+
+ // After the app initializes and reports are available the home navigation is mounted
+ // This way routing information is updated (if needed) based on the initial report ID resolved.
+ // This is usually needed after login/create account and re-launches
+ return (
+ {
+ // This state belongs to the drawer so it should always have the ReportScreen as it's initial (and only) route
+ const reportIDFromRoute = lodashGet(state, ['routes', 0, 'params', 'reportID']);
+ return (
+
+ );
+ }}
+ screens={[
+ {
+ name: SCREENS.REPORT,
+ component: ReportScreen,
+ initialParams: this.initialParams,
+ },
+ ]}
+ isMainScreen
+ />
+ );
+ }
+}
MainDrawerNavigator.propTypes = propTypes;
MainDrawerNavigator.defaultProps = defaultProps;
diff --git a/src/libs/Navigation/Navigation.js b/src/libs/Navigation/Navigation.js
index ab88b8a600c8..ae54d59f7aca 100644
--- a/src/libs/Navigation/Navigation.js
+++ b/src/libs/Navigation/Navigation.js
@@ -1,4 +1,5 @@
import _ from 'underscore';
+import lodashGet from 'lodash/get';
import {Keyboard} from 'react-native';
import {DrawerActions, getPathFromState, StackActions} from '@react-navigation/native';
import Onyx from 'react-native-onyx';
@@ -182,6 +183,19 @@ function getActiveRoute() {
: '';
}
+/**
+ * @returns {String}
+ */
+function getReportIDFromRoute() {
+ if (!navigationRef.current) {
+ return '';
+ }
+
+ const drawerState = lodashGet(navigationRef.current.getState(), ['routes', 0, 'state']);
+ const reportRoute = lodashGet(drawerState, ['routes', 0]);
+ return lodashGet(reportRoute, ['params', 'reportID'], '');
+}
+
/**
* Check whether the passed route is currently Active or not.
*
@@ -230,6 +244,7 @@ export default {
setDidTapNotification,
isNavigationReady,
setIsNavigationReady,
+ getReportIDFromRoute,
isDrawerReady,
setIsDrawerReady,
isDrawerRoute,
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index 4ae6da7fbf56..eeae8d804a8f 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -9,6 +9,7 @@ import * as ReportUtils from './ReportUtils';
import * as Localize from './Localize';
import Permissions from './Permissions';
import * as CollectionUtils from './CollectionUtils';
+import Navigation from './Navigation/Navigation';
/**
* OptionsListUtils is used to build a list options passed to the OptionsList component. Several different UI views can
@@ -22,18 +23,6 @@ Onyx.connect({
callback: val => currentUserLogin = val && val.email,
});
-let currentlyViewedReportID;
-Onyx.connect({
- key: ONYXKEYS.CURRENTLY_VIEWED_REPORTID,
- callback: val => currentlyViewedReportID = val,
-});
-
-let priorityMode;
-Onyx.connect({
- key: ONYXKEYS.NVP_PRIORITY_MODE,
- callback: val => priorityMode = val,
-});
-
let loginList;
Onyx.connect({
key: ONYXKEYS.LOGIN_LIST,
@@ -111,6 +100,9 @@ function addSMSDomainIfPhoneNumber(login) {
*/
function getPersonalDetailsForLogins(logins, personalDetails) {
const personalDetailsForLogins = {};
+ if (!personalDetails) {
+ return personalDetailsForLogins;
+ }
_.each(logins, (login) => {
let personalDetail = personalDetails[login];
if (!personalDetail) {
@@ -428,12 +420,11 @@ function isCurrentUser(userDetails) {
*
* @param {Object} reports
* @param {Object} personalDetails
- * @param {String} activeReportID
* @param {Object} options
* @returns {Object}
* @private
*/
-function getOptions(reports, personalDetails, activeReportID, {
+function getOptions(reports, personalDetails, {
reportActions = {},
betas = [],
selectedOptions = [],
@@ -451,13 +442,20 @@ function getOptions(reports, personalDetails, activeReportID, {
sortPersonalDetailsByAlphaAsc = true,
forcePolicyNamePreview = false,
}) {
- const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD;
let recentReportOptions = [];
let personalDetailsOptions = [];
const reportMapForLogins = {};
// Filter out all the reports that shouldn't be displayed
- const filteredReports = _.filter(reports, report => ReportUtils.shouldReportBeInOptionList(report, currentlyViewedReportID, isInGSDMode, currentUserLogin, iouReports, betas, policies));
+ const filteredReports = _.filter(reports, report => ReportUtils.shouldReportBeInOptionList(
+ report,
+ Navigation.getReportIDFromRoute(),
+ false,
+ currentUserLogin,
+ iouReports,
+ betas,
+ policies,
+ ));
// Sorting the reports works like this:
// - Order everything by the last message timestamp (descending)
@@ -639,7 +637,7 @@ function getSearchOptions(
searchValue = '',
betas,
) {
- return getOptions(reports, personalDetails, 0, {
+ return getOptions(reports, personalDetails, {
betas,
searchValue: searchValue.trim(),
includeRecentReports: true,
@@ -706,7 +704,7 @@ function getNewChatOptions(
selectedOptions = [],
excludeLogins = [],
) {
- return getOptions(reports, personalDetails, 0, {
+ return getOptions(reports, personalDetails, {
betas,
searchValue: searchValue.trim(),
selectedOptions,
@@ -733,7 +731,7 @@ function getMemberInviteOptions(
searchValue = '',
excludeLogins = [],
) {
- return getOptions([], personalDetails, 0, {
+ return getOptions([], personalDetails, {
betas,
searchValue: searchValue.trim(),
excludeDefaultRooms: true,
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index d78ca432f6fe..8f237cdc4efc 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -865,12 +865,12 @@ function isUnread(report) {
* @param {Object} report
* @param {String} report.iouReportID
* @param {String} currentUserLogin
- * @param {Object[]} iouReports
- * @param {String} iouReports[].ownerEmail
+ * @param {Object} iouReports
* @returns {boolean}
*/
+
function hasOutstandingIOU(report, currentUserLogin, iouReports) {
- if (!report || !report.iouReportID || report.hasOutstandingIOU) {
+ if (!report || !report.iouReportID || _.isUndefined(report.hasOutstandingIOU)) {
return false;
}
@@ -879,7 +879,11 @@ function hasOutstandingIOU(report, currentUserLogin, iouReports) {
return false;
}
- return iouReport.ownerEmail !== currentUserLogin;
+ if (iouReport.ownerEmail === currentUserEmail) {
+ return false;
+ }
+
+ return report.hasOutstandingIOU;
}
/**
@@ -890,7 +894,7 @@ function hasOutstandingIOU(report, currentUserLogin, iouReports) {
* filter out the majority of reports before filtering out very specific minority of reports.
*
* @param {Object} report
- * @param {String} currentlyViewedReportID
+ * @param {String} reportIDFromRoute
* @param {Boolean} isInGSDMode
* @param {String} currentUserLogin
* @param {Object} iouReports
@@ -898,7 +902,7 @@ function hasOutstandingIOU(report, currentUserLogin, iouReports) {
* @param {Object} policies
* @returns {boolean}
*/
-function shouldReportBeInOptionList(report, currentlyViewedReportID, isInGSDMode, currentUserLogin, iouReports, betas, policies) {
+function shouldReportBeInOptionList(report, reportIDFromRoute, isInGSDMode, currentUserLogin, iouReports, betas, policies) {
// Exclude reports that have no data because there wouldn't be anything to show in the option item.
// This can happen if data is currently loading from the server or a report is in various stages of being created.
if (!report || !report.reportID || !report.participants || _.isEmpty(report.participants)) {
@@ -908,16 +912,10 @@ function shouldReportBeInOptionList(report, currentlyViewedReportID, isInGSDMode
// Include the currently viewed report. If we excluded the currently viewed report, then there
// would be no way to highlight it in the options list and it would be confusing to users because they lose
// a sense of context.
- if (report.reportID === currentlyViewedReportID) {
+ if (report.reportID === reportIDFromRoute) {
return true;
}
- // Include unread reports when in GSD mode
- // GSD mode is specifically for focusing the user on the most relevant chats, primarily, the unread ones
- if (isInGSDMode) {
- return isUnread(report);
- }
-
// Include reports if they have a draft, are pinned, or have an outstanding IOU
// These are always relevant to the user no matter what view mode the user prefers
if (report.hasDraft || report.isPinned || hasOutstandingIOU(report, currentUserLogin, iouReports)) {
@@ -931,12 +929,18 @@ function shouldReportBeInOptionList(report, currentlyViewedReportID, isInGSDMode
}
// Exclude reports that don't have any comments
- // Archived rooms or user created policy rooms are OK to show when the don't have any comments
+ // User created policy rooms are OK to show when the don't have any comments, only if they aren't archived.
const hasNoComments = report.lastMessageTimestamp === 0;
- if (hasNoComments && (isArchivedRoom(report) || isUserCreatedPolicyRoom(report))) {
+ if (hasNoComments && (isArchivedRoom(report) || !isUserCreatedPolicyRoom(report))) {
return false;
}
+ // Include unread reports when in GSD mode
+ // GSD mode is specifically for focusing the user on the most relevant chats, primarily, the unread ones
+ if (isInGSDMode) {
+ return isUnread(report);
+ }
+
// Include default rooms for free plan policies
if (isDefaultRoom(report) && getPolicyType(report, policies) === CONST.POLICY.TYPE.FREE) {
return true;
@@ -979,6 +983,7 @@ export {
isConciergeChatReport,
hasExpensifyEmails,
hasExpensifyGuidesEmails,
+ hasOutstandingIOU,
canShowReportRecipientLocalTime,
formatReportLastMessageText,
chatIncludesConcierge,
diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js
index a73755e0fb61..e9365750daf1 100644
--- a/src/libs/SidebarUtils.js
+++ b/src/libs/SidebarUtils.js
@@ -12,7 +12,7 @@ import * as CollectionUtils from './CollectionUtils';
// keys that are connected to SidebarLinks withOnyx(). If there was a key missing from SidebarLinks and it's data was updated
// for that key, then there would be no re-render and the options wouldn't reflect the new data because SidebarUtils.getOrderedReportIDs() wouldn't be triggered.
// There are a couple of keys here which are OK to have stale data. iouReports for example, doesn't need to exist in withOnyx() because
-// when IOUs change, it also triggers a change on the reports collection. Having redudant subscriptions causes more re-renders which should be avoided.
+// when IOUs change, it also triggers a change on the reports collection. Having redundant subscriptions causes more re-renders which should be avoided.
// Session also can remain stale because the only way for the current user to change is to sign out and sign in, which would clear out all the Onyx
// data anyway and cause SidebarLinks to rerender.
@@ -29,12 +29,6 @@ Onyx.connect({
callback: val => personalDetails = val,
});
-let currentlyViewedReportID;
-Onyx.connect({
- key: ONYXKEYS.CURRENTLY_VIEWED_REPORTID,
- callback: val => currentlyViewedReportID = val,
-});
-
let priorityMode;
Onyx.connect({
key: ONYXKEYS.NVP_PRIORITY_MODE,
@@ -88,9 +82,10 @@ Onyx.connect({
});
/**
+ * @param {String} reportIDFromRoute
* @returns {String[]} An array of reportIDs sorted in the proper order
*/
-function getOrderedReportIDs() {
+function getOrderedReportIDs(reportIDFromRoute) {
let recentReportOptions = [];
const pinnedReportOptions = [];
const iouDebtReportOptions = [];
@@ -100,7 +95,7 @@ function getOrderedReportIDs() {
const isInDefaultMode = !isInGSDMode;
// Filter out all the reports that shouldn't be displayed
- const filteredReports = _.filter(reports, report => ReportUtils.shouldReportBeInOptionList(report, currentlyViewedReportID, isInGSDMode, currentUserLogin, iouReports, betas, policies));
+ const filteredReports = _.filter(reports, report => ReportUtils.shouldReportBeInOptionList(report, reportIDFromRoute, isInGSDMode, currentUserLogin, iouReports, betas, policies));
// Get all the display names for our reports in an easy to access property so we don't have to keep
// re-running the logic
@@ -142,7 +137,7 @@ function getOrderedReportIDs() {
// If the active report has a draft, we do not put it in the group of draft reports because we want it to maintain it's current position. Otherwise the report's position
// jumps around in the LHN and it's kind of confusing to the user to see the LHN reorder when they start typing a comment on a report.
- } else if (report.hasDraft && report.reportID !== currentlyViewedReportID) {
+ } else if (report.hasDraft && report.reportID !== reportIDFromRoute) {
draftReportOptions.push(report);
} else {
recentReportOptions.push(report);
diff --git a/src/libs/UnreadIndicatorUpdater/index.js b/src/libs/UnreadIndicatorUpdater/index.js
index e4b21ddea4be..4cf82b19d376 100644
--- a/src/libs/UnreadIndicatorUpdater/index.js
+++ b/src/libs/UnreadIndicatorUpdater/index.js
@@ -7,13 +7,14 @@ import * as ReportUtils from '../ReportUtils';
const reports = {};
/**
- * Updates the title and favicon of the current browser tab
- * and Mac OS or iOS dock icon with an unread indicator.
+ * Updates the title and favicon of the current browser tab and Mac OS or iOS dock icon with an unread indicator.
+ * Note: We are throttling this since listening for report changes can trigger many updates depending on how many reports
+ * a user has and how often they are updated.
*/
const throttledUpdatePageTitleAndUnreadCount = _.throttle(() => {
const totalCount = _.filter(reports, ReportUtils.isUnread).length;
updateUnread(totalCount);
-}, 1000, {leading: false});
+}, 100, {leading: false});
let connectionID;
diff --git a/src/libs/UnreadIndicatorUpdater/updateUnread/index.website.js b/src/libs/UnreadIndicatorUpdater/updateUnread/index.website.js
index 75f6d3f31d08..c39afe29551b 100644
--- a/src/libs/UnreadIndicatorUpdater/updateUnread/index.website.js
+++ b/src/libs/UnreadIndicatorUpdater/updateUnread/index.website.js
@@ -10,6 +10,10 @@ import CONFIG from '../../../CONFIG';
*/
function updateUnread(totalCount) {
const hasUnread = totalCount !== 0;
+
+ // There is a Chrome browser bug that causes the title to revert back to the previous when we are navigating back. Setting the title to an empty string
+ // seems to improve this issue.
+ document.title = '';
document.title = hasUnread ? `(${totalCount}) ${CONFIG.SITE_TITLE}` : CONFIG.SITE_TITLE;
document.getElementById('favicon').href = hasUnread ? CONFIG.FAVICON.UNREAD : CONFIG.FAVICON.DEFAULT;
}
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index ffcbfdc7b443..6af4d574bcbb 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -25,6 +25,13 @@ Onyx.connect({
allPolicies[key] = val;
},
});
+
+let lastAccessedWorkspacePolicyID = null;
+Onyx.connect({
+ key: ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID,
+ callback: value => lastAccessedWorkspacePolicyID = value,
+});
+
let sessionEmail = '';
Onyx.connect({
key: ONYXKEYS.SESSION,
@@ -83,6 +90,14 @@ function getSimplifiedPolicyObject(fullPolicyOrPolicySummary, isFromFullPolicy)
};
}
+/**
+ * Stores in Onyx the policy ID of the last workspace that was accessed by the user
+ * @param {String|null} policyID
+ */
+function updateLastAccessedWorkspace(policyID) {
+ Onyx.set(ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID, policyID);
+}
+
/**
* Used to update ALL of the policies at once. If a policy is present locally, but not in the policies object passed here it will be removed.
* @param {Object} policyCollection - object of policy key and partial policy object
@@ -125,6 +140,11 @@ function deleteWorkspace(policyID) {
const failureData = [];
const successData = [];
API.write('DeleteWorkspace', {policyID}, {optimisticData, successData, failureData});
+
+ // Reset the lastAccessedWorkspacePolicyID
+ if (policyID === lastAccessedWorkspacePolicyID) {
+ updateLastAccessedWorkspace(null);
+ }
}
/**
@@ -633,14 +653,6 @@ function updateCustomUnitRate(policyID, currentCustomUnitRate, customUnitID, new
}, {optimisticData, successData, failureData});
}
-/**
- * Stores in Onyx the policy ID of the last workspace that was accessed by the user
- * @param {String} policyID
- */
-function updateLastAccessedWorkspace(policyID) {
- Onyx.set(ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID, policyID);
-}
-
/**
* Removes an error after trying to delete a member
*
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 901fad841e7d..fc9b2e5a8518 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -41,12 +41,6 @@ Onyx.connect({
},
});
-let lastViewedReportID;
-Onyx.connect({
- key: ONYXKEYS.CURRENTLY_VIEWED_REPORTID,
- callback: val => lastViewedReportID = val ? Number(val) : null,
-});
-
const allReports = {};
let conciergeChatReportID;
const typingWatchTimers = {};
@@ -946,13 +940,6 @@ function handleReportChanged(report) {
}
}
-/**
- * @param {String} reportID
- */
-function updateCurrentlyViewedReportID(reportID) {
- Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, String(reportID));
-}
-
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT,
callback: handleReportChanged,
@@ -1205,40 +1192,6 @@ function navigateToConciergeChat() {
Navigation.navigate(ROUTES.getReportRoute(conciergeChatReportID));
}
-/**
- * Creates a policy room, fetches it, and navigates to it.
- * @param {String} policyID
- * @param {String} reportName
- * @param {String} visibility
- * @return {Promise}
- */
-function createPolicyRoom(policyID, reportName, visibility) {
- Onyx.set(ONYXKEYS.IS_LOADING_CREATE_POLICY_ROOM, true);
- return DeprecatedAPI.CreatePolicyRoom({policyID, reportName, visibility})
- .then((response) => {
- if (response.jsonCode === CONST.JSON_CODE.UNABLE_TO_RETRY) {
- Growl.error(Localize.translateLocal('newRoomPage.growlMessageOnError'));
- return;
- }
-
- if (response.jsonCode !== CONST.JSON_CODE.SUCCESS) {
- Growl.error(response.message);
- return;
- }
-
- return fetchChatReportsByIDs([response.reportID]);
- })
- .then((chatReports) => {
- const reportID = lodashGet(_.first(_.values(chatReports)), 'reportID', '');
- if (!reportID) {
- Log.error('Unable to grab policy room after creation', reportID);
- return;
- }
- Navigation.navigate(ROUTES.getReportRoute(reportID));
- })
- .finally(() => Onyx.set(ONYXKEYS.IS_LOADING_CREATE_POLICY_ROOM, false));
-}
-
/**
* Add a policy report (workspace room) optimistically and navigate to it.
*
@@ -1261,7 +1214,7 @@ function addPolicyReport(policy, reportName, visibility) {
);
// Onyx.set is used on the optimistic data so that it is present before navigating to the workspace room. With Onyx.merge the workspace room reportID is not present when
- // storeCurrentlyViewedReport is called on the ReportScreen, so fetchChatReportsByIDs is called which is unnecessary since the optimistic data will be stored in Onyx.
+ // fetchReportIfNeeded is called on the ReportScreen, so fetchChatReportsByIDs is called which is unnecessary since the optimistic data will be stored in Onyx.
// If there was an error creating the room, then fetchChatReportsByIDs throws an error and the user is navigated away from the report instead of showing the RBR error message.
// Therefore, Onyx.set is used instead of Onyx.merge.
const optimisticData = [
@@ -1433,7 +1386,7 @@ function viewNewReportAction(reportID, action) {
}
// If we are currently viewing this report do not show a notification.
- if (reportID === lastViewedReportID && Visibility.isVisible()) {
+ if (reportID === Navigation.getReportIDFromRoute() && Visibility.isVisible()) {
Log.info('[LOCAL_NOTIFICATION] No notification because it was a comment for the current report');
return;
}
@@ -1469,7 +1422,7 @@ Onyx.connect({
initWithStoredValues: false,
callback: (actions, key) => {
// reportID can be derived from the Onyx key
- const reportID = parseInt(key.split('_')[1], 10);
+ const reportID = key.split('_')[1];
if (!reportID) {
return;
}
@@ -1522,7 +1475,6 @@ export {
saveReportComment,
broadcastUserIsTyping,
togglePinnedState,
- updateCurrentlyViewedReportID,
editReportComment,
saveReportActionDraft,
deleteReportComment,
@@ -1530,7 +1482,6 @@ export {
syncChatAndIOUReports,
navigateToConciergeChat,
setReportWithDraft,
- createPolicyRoom,
addPolicyReport,
navigateToConciergeChatAndDeletePolicyReport,
setIsComposerFullSize,
diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js
index 7554ce05a040..4e9ccc88478c 100644
--- a/src/libs/actions/User.js
+++ b/src/libs/actions/User.js
@@ -30,12 +30,6 @@ Onyx.connect({
},
});
-let currentlyViewedReportID = '';
-Onyx.connect({
- key: ONYXKEYS.CURRENTLY_VIEWED_REPORTID,
- callback: val => currentlyViewedReportID = val || '',
-});
-
/**
* Changes a password for a given account
*
@@ -215,7 +209,6 @@ function setSecondaryLoginAndNavigate(login, password) {
*/
function validateLogin(accountID, validateCode) {
const isLoggedIn = !_.isEmpty(sessionAuthToken);
- const redirectRoute = isLoggedIn ? ROUTES.getReportRoute(currentlyViewedReportID) : ROUTES.HOME;
Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, isLoading: true});
DeprecatedAPI.ValidateEmail({
@@ -237,7 +230,7 @@ function validateLogin(accountID, validateCode) {
}
}).finally(() => {
Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false});
- Navigation.navigate(redirectRoute);
+ Navigation.navigate(ROUTES.HOME);
});
}
diff --git a/src/libs/deprecatedAPI.js b/src/libs/deprecatedAPI.js
index 68caeea5443d..4fc2937560f6 100644
--- a/src/libs/deprecatedAPI.js
+++ b/src/libs/deprecatedAPI.js
@@ -526,19 +526,6 @@ function GetReportSummaryList(parameters) {
return Network.post(commandName, {...parameters, returnValueList: 'reportSummaryList'});
}
-/**
- * @param {Object} parameters
- * @param {String} parameters.policyID
- * @param {String} parameters.reportName
- * @param {String} parameters.visibility
- * @return {Promise}
- */
-function CreatePolicyRoom(parameters) {
- const commandName = 'CreatePolicyRoom';
- requireParameters(['policyID', 'reportName', 'visibility'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
/**
* Transfer Wallet balance and takes either the bankAccoundID or fundID
* @param {Object} parameters
@@ -570,7 +557,6 @@ export {
ChangePassword,
CreateChatReport,
CreateLogin,
- CreatePolicyRoom,
DeleteLogin,
Get,
GetStatementPDF,
diff --git a/src/pages/ReimbursementAccount/BankAccountManualStep.js b/src/pages/ReimbursementAccount/BankAccountManualStep.js
index afaa3b1c7851..65f214a8b2c6 100644
--- a/src/pages/ReimbursementAccount/BankAccountManualStep.js
+++ b/src/pages/ReimbursementAccount/BankAccountManualStep.js
@@ -37,7 +37,10 @@ class BankAccountManualStep extends React.Component {
const errorFields = {};
const routingNumber = values.routingNumber && values.routingNumber.trim();
- if (!values.accountNumber || !CONST.BANK_ACCOUNT.REGEX.US_ACCOUNT_NUMBER.test(values.accountNumber.trim())) {
+ if (
+ !values.accountNumber
+ || (!CONST.BANK_ACCOUNT.REGEX.US_ACCOUNT_NUMBER.test(values.accountNumber.trim()) && !CONST.BANK_ACCOUNT.REGEX.MASKED_US_ACCOUNT_NUMBER.test(values.accountNumber.trim()))
+ ) {
errorFields.accountNumber = this.props.translate('bankAccount.error.accountNumber');
}
if (!routingNumber || !CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(routingNumber) || !ValidationUtils.isValidRoutingNumber(routingNumber)) {
@@ -91,6 +94,7 @@ class BankAccountManualStep extends React.Component {
)}
+ defaultValue={ReimbursementAccountUtils.getDefaultStateForField(this.props, 'acceptTerms', true)}
shouldSaveDraft
/>
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index bdf3a608b71f..4cb8dc947ec9 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -115,7 +115,8 @@ class ReportScreen extends React.Component {
}
componentDidMount() {
- this.storeCurrentlyViewedReport();
+ this.fetchReportIfNeeded();
+ toggleReportActionComposeView(true);
this.removeViewportResizeListener = addViewportResizeListener(this.updateViewportOffsetTop);
}
@@ -123,7 +124,9 @@ class ReportScreen extends React.Component {
if (this.props.route.params.reportID === prevProps.route.params.reportID) {
return;
}
- this.storeCurrentlyViewedReport();
+
+ this.fetchReportIfNeeded();
+ toggleReportActionComposeView(true);
}
componentWillUnmount() {
@@ -149,16 +152,9 @@ class ReportScreen extends React.Component {
return !getReportID(this.props.route) || isLoadingInitialReportActions || !this.props.report.reportID;
}
- /**
- * Persists the currently viewed report id
- */
- storeCurrentlyViewedReport() {
+ fetchReportIfNeeded() {
const reportIDFromPath = getReportID(this.props.route);
- // Always reset the state of the composer view when the current reportID changes
- toggleReportActionComposeView(true);
- Report.updateCurrentlyViewedReportID(reportIDFromPath);
-
// It possible that we may not have the report object yet in Onyx yet e.g. we navigated to a URL for an accessible report that
// is not stored locally yet. If props.report.reportID exists, then the report has been stored locally and nothing more needs to be done.
// If it doesn't exist, then we fetch the report from the API.
diff --git a/src/pages/home/report/ReportActionItemCreated.js b/src/pages/home/report/ReportActionItemCreated.js
index 109985bdc2bf..96558d6facb8 100644
--- a/src/pages/home/report/ReportActionItemCreated.js
+++ b/src/pages/home/report/ReportActionItemCreated.js
@@ -15,7 +15,7 @@ import reportPropTypes from '../../reportPropTypes';
const propTypes = {
/** The id of the report */
- reportID: PropTypes.number.isRequired,
+ reportID: PropTypes.string.isRequired,
/** The report currently being looked at */
report: reportPropTypes,
diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js
index cfa72719de47..22a95997fab7 100644
--- a/src/pages/home/sidebar/SidebarLinks.js
+++ b/src/pages/home/sidebar/SidebarLinks.js
@@ -58,8 +58,8 @@ const propTypes = {
avatar: PropTypes.string,
}),
- /** Currently viewed reportID */
- currentlyViewedReportID: PropTypes.string,
+ /** Current reportID from the route in react navigation state object */
+ reportIDFromRoute: PropTypes.string,
/** Whether we are viewing below the responsive breakpoint */
isSmallScreenWidth: PropTypes.bool.isRequired,
@@ -77,7 +77,7 @@ const defaultProps = {
currentUserPersonalDetails: {
avatar: ReportUtils.getDefaultAvatar(),
},
- currentlyViewedReportID: '',
+ reportIDFromRoute: '',
priorityMode: CONST.PRIORITY_MODE.DEFAULT,
};
@@ -91,7 +91,7 @@ class SidebarLinks extends React.Component {
if (_.isEmpty(this.props.personalDetails)) {
return null;
}
- const optionListItems = SidebarUtils.getOrderedReportIDs();
+ const optionListItems = SidebarUtils.getOrderedReportIDs(this.props.reportIDFromRoute);
return (
option.toString() === this.props.currentlyViewedReportID
+ option => option.toString() === this.props.reportIDFromRoute
))}
onSelectRow={(option) => {
Navigation.navigate(ROUTES.getReportRoute(option.reportID));
@@ -177,9 +177,6 @@ export default compose(
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS,
},
- currentlyViewedReportID: {
- key: ONYXKEYS.CURRENTLY_VIEWED_REPORTID,
- },
priorityMode: {
key: ONYXKEYS.NVP_PRIORITY_MODE,
},
diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
index ff3b97c3f778..e2b92deb3c60 100644
--- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
+++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
@@ -28,6 +28,9 @@ const propTypes = {
/* Callback function before the menu is hidden */
onHideCreateMenu: PropTypes.func,
+ /** reportID in the current navigation state */
+ reportIDFromRoute: PropTypes.string,
+
...sidebarPropTypes,
};
const defaultProps = {
@@ -112,6 +115,7 @@ class BaseSidebarScreen extends Component {
onAvatarClick={this.navigateToSettings}
isSmallScreenWidth={this.props.isSmallScreenWidth}
isDrawerOpen={this.props.isDrawerOpen}
+ reportIDFromRoute={this.props.reportIDFromRoute}
/>
{
let baseSidebarScreen = null;
@@ -40,7 +39,6 @@ SidebarScreen.defaultProps = sidebarDefaultProps;
SidebarScreen.displayName = 'SidebarScreen';
export default compose(
- withNavigation,
withLocalize,
withWindowDimensions,
withOnyx({
diff --git a/src/pages/home/sidebar/SidebarScreen/index.native.js b/src/pages/home/sidebar/SidebarScreen/index.native.js
index 2c865c83900d..e2cb2838efe8 100755
--- a/src/pages/home/sidebar/SidebarScreen/index.native.js
+++ b/src/pages/home/sidebar/SidebarScreen/index.native.js
@@ -6,7 +6,6 @@ import withLocalize from '../../../../components/withLocalize';
import ONYXKEYS from '../../../../ONYXKEYS';
import {sidebarPropTypes, sidebarDefaultProps} from './sidebarPropTypes';
import BaseSidebarScreen from './BaseSidebarScreen';
-import withNavigation from '../../../../components/withNavigation';
// eslint-disable-next-line react/jsx-props-no-spreading
const SidebarScreen = props => ;
@@ -16,7 +15,6 @@ SidebarScreen.defaultProps = sidebarDefaultProps;
SidebarScreen.displayName = 'SidebarScreen';
export default compose(
- withNavigation,
withLocalize,
withWindowDimensions,
withOnyx({
diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js
index 898840e0ed7f..654e2ce8cc0f 100644
--- a/src/pages/workspace/WorkspaceNewRoomPage.js
+++ b/src/pages/workspace/WorkspaceNewRoomPage.js
@@ -56,6 +56,7 @@ class WorkspaceNewRoomPage extends React.Component {
};
this.validateAndAddPolicyReport = this.validateAndAddPolicyReport.bind(this);
+ this.focusRoomNameInput = this.focusRoomNameInput.bind(this);
}
componentDidMount() {
@@ -127,6 +128,14 @@ class WorkspaceNewRoomPage extends React.Component {
}));
}
+ focusRoomNameInput() {
+ if (!this.roomNameInputRef) {
+ return;
+ }
+
+ this.roomNameInputRef.focus();
+ }
+
render() {
if (!Permissions.canUsePolicyRooms(this.props.betas)) {
Log.info('Not showing create Policy Room page since user is not on policy rooms beta');
@@ -141,7 +150,7 @@ class WorkspaceNewRoomPage extends React.Component {
}));
return (
-
+
Navigation.dismissModal()}
@@ -149,6 +158,7 @@ class WorkspaceNewRoomPage extends React.Component {
this.roomNameInputRef = el}
policyID={this.state.policyID}
errorText={this.state.errors.roomName}
onChangeText={roomName => this.clearErrorAndSetValue('roomName', roomName)}
diff --git a/src/setup/index.js b/src/setup/index.js
index 61d55dba207d..b0d2d7687cc8 100644
--- a/src/setup/index.js
+++ b/src/setup/index.js
@@ -22,7 +22,6 @@ export default function () {
Onyx.init({
keys: ONYXKEYS,
safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
- keysToDisableSyncEvents: [ONYXKEYS.CURRENTLY_VIEWED_REPORTID],
captureMetrics: Metrics.canCaptureOnyxMetrics(),
initialKeyStates: {
diff --git a/src/styles/styles.js b/src/styles/styles.js
index bba8f24ed62d..330df6e84946 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -1122,9 +1122,7 @@ const styles = {
},
popoverMenuText: {
- fontFamily: fontFamily.GTA_BOLD,
fontSize: variables.fontSizeNormal,
- fontWeight: fontWeightBold,
color: themeColors.heading,
maxWidth: 240,
},
@@ -1858,6 +1856,10 @@ const styles = {
fontSize: 15,
},
+ blockingViewContainer: {
+ paddingBottom: variables.contentHeaderHeight,
+ },
+
defaultModalContainer: {
backgroundColor: themeColors.componentBG,
borderColor: colors.transparent,
diff --git a/src/styles/utilities/spacing.js b/src/styles/utilities/spacing.js
index 53c220f41220..b638006f59e6 100644
--- a/src/styles/utilities/spacing.js
+++ b/src/styles/utilities/spacing.js
@@ -353,4 +353,8 @@ export default {
pb10Percentage: {
paddingBottom: '10%',
},
+
+ gap1: {
+ gap: 4,
+ },
};
diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js
index 9531266efe2b..7f160add48a4 100644
--- a/tests/ui/UnreadIndicatorsTest.js
+++ b/tests/ui/UnreadIndicatorsTest.js
@@ -26,7 +26,8 @@ beforeAll(() => {
// simulate data arriving we will just set it into Onyx directly with Onyx.merge() or Onyx.set() etc.
global.fetch = TestHelper.getGlobalFetchMock();
- jest.setTimeout(15000);
+ // We need a large timeout here as we are lazy loading React Navigation screens and this test is running against the entire mounted App
+ jest.setTimeout(30000);
Linking.setInitialURL('https://new.expensify.com/r/1');
appSetup();
});
diff --git a/tests/unit/LHNFilterTest.js b/tests/unit/LHNFilterTest.js
index cd9e4d90add2..4a0e0b91a02c 100644
--- a/tests/unit/LHNFilterTest.js
+++ b/tests/unit/LHNFilterTest.js
@@ -10,7 +10,6 @@ jest.mock('../../src/libs/Permissions');
const ONYXKEYS = {
PERSONAL_DETAILS: 'personalDetails',
- CURRENTLY_VIEWED_REPORTID: 'currentlyViewedReportID',
NVP_PRIORITY_MODE: 'nvp_priorityMode',
SESSION: 'session',
BETAS: 'betas',
@@ -248,17 +247,16 @@ describe('Sidebar', () => {
// Given these combinations of booleans which result in the report being filtered out (not shown).
const booleansWhichRemovesInactiveReport = [
- // isUserCreatedPolicyRoom
- JSON.stringify([false, false, true, false, false, false, false]),
+ JSON.stringify([false, false, false, false, false, false, false]),
- // isUserCreatedPolicyRoom, isUnread
- JSON.stringify([false, false, true, false, true, false, false]),
+ // isUnread
+ JSON.stringify([false, false, false, false, true, false, false]),
- // isUserCreatedPolicyRoom, hasAddWorkspaceError
- JSON.stringify([false, false, true, true, false, false, false]),
+ // hasAddWorkspaceError, isUnread
+ JSON.stringify([false, false, false, true, true, false, false]),
- // isUserCreatedPolicyRoom, hasAddWorkspaceError, isUnread
- JSON.stringify([false, false, true, true, true, false, false]),
+ // hasAddWorkspaceError
+ JSON.stringify([false, false, false, true, false, false, false]),
// isArchived
JSON.stringify([false, true, false, false, false, false, false]),
@@ -306,7 +304,7 @@ describe('Sidebar', () => {
...LHNTestUtils.getAdvancedFakeReport(...boolArr),
policyID: policy.policyID,
};
- const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks();
+ const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks(report1.reportID);
return waitForPromisesToResolve()
@@ -314,7 +312,6 @@ describe('Sidebar', () => {
.then(() => Onyx.multiSet({
[ONYXKEYS.BETAS]: betas,
[ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
- [ONYXKEYS.CURRENTLY_VIEWED_REPORTID]: report1.reportID.toString(),
[`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1,
[`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2,
[`${ONYXKEYS.COLLECTION.POLICY}${policy.policyID}`]: policy,
@@ -340,8 +337,6 @@ describe('Sidebar', () => {
describe('in #focus mode', () => {
it('hides unread chats', () => {
- const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks();
-
// Given the sidebar is rendered in #focus mode (hides read chats)
// with report 1 and 2 having unread actions
const report1 = {
@@ -353,6 +348,7 @@ describe('Sidebar', () => {
lastReadSequenceNumber: LHNTestUtils.TEST_MAX_SEQUENCE_NUMBER - 1,
};
const report3 = LHNTestUtils.getFakeReport(['email5@test.com', 'email6@test.com']);
+ let sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks(report1.reportID);
return waitForPromisesToResolve()
@@ -360,7 +356,6 @@ describe('Sidebar', () => {
.then(() => Onyx.multiSet({
[ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD,
[ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
- [ONYXKEYS.CURRENTLY_VIEWED_REPORTID]: report1.reportID.toString(),
[`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1,
[`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2,
[`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3,
@@ -391,7 +386,10 @@ describe('Sidebar', () => {
})
// When report 2 becomes the active report
- .then(() => Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, report2.reportID.toString()))
+ .then(() => {
+ sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks(report2.reportID);
+ return waitForPromisesToResolve();
+ })
// Then report 1 should now disappear
.then(() => {
@@ -399,5 +397,242 @@ describe('Sidebar', () => {
expect(sidebarLinks.queryAllByText(/One, Two/)).toHaveLength(0);
});
});
+
+ it('always shows pinned and draft chats', () => {
+ // Given a draft report and a pinned report
+ const draftReport = {
+ ...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com']),
+ hasDraft: true,
+ };
+ const pinnedReport = {
+ ...LHNTestUtils.getFakeReport(['email3@test.com', 'email4@test.com']),
+ isPinned: true,
+ };
+ const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks(draftReport.reportID);
+
+ return waitForPromisesToResolve()
+
+ // When Onyx is updated to contain that data and the sidebar re-renders
+ .then(() => Onyx.multiSet({
+ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD,
+ [ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
+ [`${ONYXKEYS.COLLECTION.REPORT}${draftReport.reportID}`]: draftReport,
+ [`${ONYXKEYS.COLLECTION.REPORT}${pinnedReport.reportID}`]: pinnedReport,
+ }))
+
+ // Then both reports are visible
+ .then(() => {
+ const displayNames = sidebarLinks.queryAllByA11yLabel('Chat user display names');
+ expect(displayNames).toHaveLength(2);
+ expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Three, Four');
+ expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
+ });
+ });
+
+ it('archived rooms are displayed only when they have unread messages', () => {
+ // Given an archived chat report, an archived default policy room, and an archived user created policy room
+ const archivedReport = {
+ ...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com']),
+ statusNum: CONST.REPORT.STATUS.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ };
+ const archivedPolicyRoomReport = {
+ ...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com']),
+ chatType: CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE,
+ statusNum: CONST.REPORT.STATUS.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ };
+ const archivedUserCreatedPolicyRoomReport = {
+ ...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com']),
+ chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM,
+ statusNum: CONST.REPORT.STATUS.CLOSED,
+ stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
+ };
+ const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks();
+
+ return waitForPromisesToResolve()
+
+ // When Onyx is updated to contain that data and the sidebar re-renders
+ .then(() => Onyx.multiSet({
+ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD,
+ [ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
+ [`${ONYXKEYS.COLLECTION.REPORT}${archivedReport.reportID}`]: archivedReport,
+ [`${ONYXKEYS.COLLECTION.REPORT}${archivedPolicyRoomReport.reportID}`]: archivedPolicyRoomReport,
+ [`${ONYXKEYS.COLLECTION.REPORT}${archivedUserCreatedPolicyRoomReport.reportID}`]: archivedUserCreatedPolicyRoomReport,
+ }))
+
+ // Then neither reports are visible
+ .then(() => {
+ const displayNames = sidebarLinks.queryAllByA11yLabel('Chat user display names');
+ expect(displayNames).toHaveLength(0);
+ })
+
+ // When they have unread messages
+ .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${archivedReport.reportID}`, {
+ lastReadSequenceNumber: LHNTestUtils.TEST_MAX_SEQUENCE_NUMBER - 1,
+ }))
+ .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${archivedPolicyRoomReport.reportID}`, {
+ lastReadSequenceNumber: LHNTestUtils.TEST_MAX_SEQUENCE_NUMBER - 1,
+ }))
+ .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${archivedUserCreatedPolicyRoomReport.reportID}`, {
+ lastReadSequenceNumber: LHNTestUtils.TEST_MAX_SEQUENCE_NUMBER - 1,
+ }))
+
+ // Then they are all visible
+ .then(() => {
+ const displayNames = sidebarLinks.queryAllByA11yLabel('Chat user display names');
+ expect(displayNames).toHaveLength(3);
+ });
+ });
+
+ it('policy rooms are displayed only when they have unread messages', () => {
+ // Given a default policy room and user created policy room
+ const policyRoomReport = {
+ ...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com']),
+ chatType: CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE,
+ };
+ const userCreatedPolicyRoomReport = {
+ ...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com']),
+ chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM,
+ };
+ const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks();
+
+ return waitForPromisesToResolve()
+
+ // When Onyx is updated to contain that data and the sidebar re-renders
+ .then(() => Onyx.multiSet({
+ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD,
+ [ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
+ [`${ONYXKEYS.COLLECTION.REPORT}${policyRoomReport.reportID}`]: policyRoomReport,
+ [`${ONYXKEYS.COLLECTION.REPORT}${userCreatedPolicyRoomReport.reportID}`]: userCreatedPolicyRoomReport,
+ }))
+
+ // Then neither reports are visible
+ .then(() => {
+ const displayNames = sidebarLinks.queryAllByA11yLabel('Chat user display names');
+ expect(displayNames).toHaveLength(0);
+ })
+
+ // When they both have unread messages
+ .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${policyRoomReport.reportID}`, {
+ lastReadSequenceNumber: LHNTestUtils.TEST_MAX_SEQUENCE_NUMBER - 1,
+ }))
+ .then(() => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${userCreatedPolicyRoomReport.reportID}`, {
+ lastReadSequenceNumber: LHNTestUtils.TEST_MAX_SEQUENCE_NUMBER - 1,
+ }))
+
+ // Then both rooms are visible
+ .then(() => {
+ const displayNames = sidebarLinks.queryAllByA11yLabel('Chat user display names');
+ expect(displayNames).toHaveLength(2);
+ });
+ });
+ });
+
+ describe('all combinations of hasComments, isArchived, isUserCreatedPolicyRoom, hasAddWorkspaceError, isUnread, isPinned, hasDraft', () => {
+ // Given a report that is the active report and doesn't change
+ const report1 = LHNTestUtils.getFakeReport(['email3@test.com', 'email4@test.com']);
+
+ // Given a free policy that doesn't change
+ const policy = {
+ name: 'Policy One',
+ policyID: '1',
+ type: CONST.POLICY.TYPE.FREE,
+ };
+
+ // Given the user is in all betas
+ const betas = [
+ CONST.BETAS.DEFAULT_ROOMS,
+ CONST.BETAS.POLICY_ROOMS,
+ CONST.BETAS.POLICY_EXPENSE_CHAT,
+ ];
+
+ // Given there are 7 boolean variables tested in the filtering logic:
+ // 1. hasComments
+ // 2. isArchived
+ // 3. isUserCreatedPolicyRoom
+ // 4. hasAddWorkspaceError
+ // 5. isUnread
+ // 6. isPinned
+ // 7. hasDraft
+ // There is one setting not represented here, which is hasOutstandingIOU. In order to test that setting, there must be
+ // additional reports in Onyx, so it's being left out for now. It's identical to the logic for hasDraft and isPinned though.
+
+ // Given these combinations of booleans which result in the report being filtered out (not shown).
+ const booleansWhichRemovesInactiveReport = [
+ JSON.stringify([false, false, false, false, false, false, false]),
+ JSON.stringify([false, false, false, true, false, false, false]),
+ JSON.stringify([false, false, false, false, true, false, false]),
+ JSON.stringify([false, false, false, true, true, false, false]),
+ JSON.stringify([false, false, true, false, false, false, false]),
+ JSON.stringify([false, false, true, true, false, false, false]),
+ JSON.stringify([false, true, false, false, false, false, false]),
+ JSON.stringify([false, true, false, false, true, false, false]),
+ JSON.stringify([false, true, false, true, false, false, false]),
+ JSON.stringify([false, true, false, true, true, false, false]),
+ JSON.stringify([false, true, true, false, false, false, false]),
+ JSON.stringify([false, true, true, false, true, false, false]),
+ JSON.stringify([false, true, true, true, false, false, false]),
+ JSON.stringify([false, true, true, true, true, false, false]),
+ JSON.stringify([true, false, false, false, false, false, false]),
+ JSON.stringify([true, false, false, true, false, false, false]),
+ JSON.stringify([true, false, true, false, false, false, false]),
+ JSON.stringify([true, false, true, true, false, false, false]),
+ JSON.stringify([true, true, false, false, false, false, false]),
+ JSON.stringify([true, true, false, true, false, false, false]),
+ JSON.stringify([true, true, true, false, false, false, false]),
+ JSON.stringify([true, true, true, true, false, false, false]),
+ ];
+
+ // When every single combination of those booleans is tested
+
+ // Taken from https://stackoverflow.com/a/39734979/9114791 to generate all possible boolean combinations
+ const AMOUNT_OF_VARIABLES = 7;
+ // eslint-disable-next-line no-bitwise
+ for (let i = 0; i < (1 << AMOUNT_OF_VARIABLES); i++) {
+ const boolArr = [];
+ for (let j = AMOUNT_OF_VARIABLES - 1; j >= 0; j--) {
+ // eslint-disable-next-line no-bitwise
+ boolArr.push(Boolean(i & (1 << j)));
+ }
+
+ // To test a failing set of conditions, comment out the for loop above and then use a hardcoded array
+ // for the specific case that's failing. You can then debug the code to see why the test is not passing.
+ // const boolArr = [false, false, false, true, false, false, false];
+
+ it(`the booleans ${JSON.stringify(boolArr)}`, () => {
+ const report2 = {
+ ...LHNTestUtils.getAdvancedFakeReport(...boolArr),
+ policyID: policy.policyID,
+ };
+ const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks(report1.reportID);
+
+ return waitForPromisesToResolve()
+
+ // When Onyx is updated to contain that data and the sidebar re-renders
+ .then(() => Onyx.multiSet({
+ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD,
+ [ONYXKEYS.BETAS]: betas,
+ [ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
+ [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1,
+ [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2,
+ [`${ONYXKEYS.COLLECTION.POLICY}${policy.policyID}`]: policy,
+ }))
+
+ // Then depending on the outcome, either one or two reports are visible
+ .then(() => {
+ if (booleansWhichRemovesInactiveReport.indexOf(JSON.stringify(boolArr)) > -1) {
+ // Only one report visible
+ expect(sidebarLinks.queryAllByA11yHint('Navigates to a chat')).toHaveLength(1);
+ expect(sidebarLinks.queryAllByA11yLabel('Chat user display names')).toHaveLength(1);
+ const displayNames = sidebarLinks.queryAllByA11yLabel('Chat user display names');
+ expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Three, Four');
+ } else {
+ // Both reports visible
+ expect(sidebarLinks.queryAllByA11yHint('Navigates to a chat')).toHaveLength(2);
+ }
+ });
+ });
+ }
});
});
diff --git a/tests/unit/LHNOrderTest.js b/tests/unit/LHNOrderTest.js
index 7f8c0a974ff6..4a7bcf4f6db6 100644
--- a/tests/unit/LHNOrderTest.js
+++ b/tests/unit/LHNOrderTest.js
@@ -10,7 +10,6 @@ jest.mock('../../src/libs/Permissions');
const ONYXKEYS = {
PERSONAL_DETAILS: 'personalDetails',
- CURRENTLY_VIEWED_REPORTID: 'currentlyViewedReportID',
NVP_PRIORITY_MODE: 'nvp_priorityMode',
SESSION: 'session',
BETAS: 'betas',
@@ -63,10 +62,9 @@ describe('Sidebar', () => {
});
it('contains one report when a report is in Onyx', () => {
- const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks();
-
// Given a single report
const report = LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com']);
+ const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks(report.reportID);
return waitForPromisesToResolve()
@@ -74,7 +72,6 @@ describe('Sidebar', () => {
.then(() => Onyx.multiSet({
[ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT,
[ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
- [ONYXKEYS.CURRENTLY_VIEWED_REPORTID]: report.reportID.toString(),
[`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report,
}))
@@ -123,8 +120,6 @@ describe('Sidebar', () => {
});
it('doesn\'t change the order when adding a draft to the active report', () => {
- const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks();
-
// Given three reports in the recently updated order of 3, 2, 1
// And the first report has a draft
// And the currently viewed report is the first report
@@ -134,15 +129,14 @@ describe('Sidebar', () => {
};
const report2 = LHNTestUtils.getFakeReport(['email3@test.com', 'email4@test.com'], 2);
const report3 = LHNTestUtils.getFakeReport(['email5@test.com', 'email6@test.com'], 1);
- const currentlyViewedReportID = report1.reportID;
-
+ const reportIDFromRoute = report1.reportID;
+ const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks(reportIDFromRoute);
return waitForPromisesToResolve()
// When Onyx is updated with the data and the sidebar re-renders
.then(() => Onyx.multiSet({
[ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT,
[ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
- [ONYXKEYS.CURRENTLY_VIEWED_REPORTID]: currentlyViewedReportID.toString(),
[`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1,
[`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2,
[`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3,
@@ -196,8 +190,6 @@ describe('Sidebar', () => {
});
it('reorders the reports to keep draft reports on top', () => {
- const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks();
-
// Given three reports in the recently updated order of 3, 2, 1
// And the second report has a draft
// And the currently viewed report is the second report
@@ -207,7 +199,8 @@ describe('Sidebar', () => {
hasDraft: true,
};
const report3 = LHNTestUtils.getFakeReport(['email5@test.com', 'email6@test.com'], 1);
- const currentlyViewedReportID = report2.reportID;
+ const reportIDFromRoute = report2.reportID;
+ let sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks(reportIDFromRoute);
return waitForPromisesToResolve()
@@ -215,14 +208,18 @@ describe('Sidebar', () => {
.then(() => Onyx.multiSet({
[ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT,
[ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
- [ONYXKEYS.CURRENTLY_VIEWED_REPORTID]: currentlyViewedReportID.toString(),
[`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1,
[`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2,
[`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3,
}))
// When the currently active chat is switched to report 1 (the one on the bottom)
- .then(() => Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, '1'))
+ .then(() => {
+ // 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.
+ sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks('1');
+ return waitForPromisesToResolve();
+ })
// Then the order of the reports should be 2 > 3 > 1
// ^--- (2 goes to the front and pushes 3 down)
@@ -302,8 +299,6 @@ describe('Sidebar', () => {
});
it('sorts chats by pinned > IOU > draft', () => {
- const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks();
-
// Given three reports in the recently updated order of 3, 2, 1
// with the current user set to email9@ (someone not participating in any of the chats)
// with a report that has a draft, a report that is pinned, and
@@ -331,9 +326,10 @@ describe('Sidebar', () => {
currency: 'USD',
chatReportID: report3.reportID,
};
- report3.iouReportID = iouReport.reportID.toString();
- const currentlyViewedReportID = report2.reportID;
+ report3.iouReportID = iouReport.reportID;
+ const reportIDFromRoute = report2.reportID;
const currentlyLoggedInUserEmail = 'email9@test.com';
+ const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks(reportIDFromRoute);
return waitForPromisesToResolve()
@@ -341,7 +337,6 @@ describe('Sidebar', () => {
.then(() => Onyx.multiSet({
[ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT,
[ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
- [ONYXKEYS.CURRENTLY_VIEWED_REPORTID]: currentlyViewedReportID.toString(),
[ONYXKEYS.SESSION]: {email: currentlyLoggedInUserEmail},
[`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1,
[`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2,
@@ -364,8 +359,6 @@ describe('Sidebar', () => {
});
it('alphabetizes all the chats that are pinned', () => {
- const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks();
-
// Given three reports in the recently updated order of 3, 2, 1
// and they are all pinned
const report1 = {
@@ -384,14 +377,13 @@ describe('Sidebar', () => {
...LHNTestUtils.getFakeReport(['email7@test.com', 'email8@test.com'], 0),
isPinned: true,
};
-
+ const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks('0');
return waitForPromisesToResolve()
// When Onyx is updated with the data and the sidebar re-renders
.then(() => Onyx.multiSet({
[ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT,
[ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
- [ONYXKEYS.CURRENTLY_VIEWED_REPORTID]: '0',
[`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1,
[`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2,
[`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3,
@@ -421,8 +413,6 @@ describe('Sidebar', () => {
});
it('alphabetizes all the chats that have drafts', () => {
- const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks();
-
// Given three reports in the recently updated order of 3, 2, 1
// and they all have drafts
const report1 = {
@@ -441,14 +431,13 @@ describe('Sidebar', () => {
...LHNTestUtils.getFakeReport(['email7@test.com', 'email8@test.com'], 0),
hasDraft: true,
};
-
+ const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks('0');
return waitForPromisesToResolve()
// When Onyx is updated with the data and the sidebar re-renders
.then(() => Onyx.multiSet({
[ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT,
[ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
- [ONYXKEYS.CURRENTLY_VIEWED_REPORTID]: '0',
[`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1,
[`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2,
[`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3,
@@ -478,8 +467,6 @@ describe('Sidebar', () => {
});
it('puts archived chats last', () => {
- const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks();
-
// Given three reports, with the first report being archived
const report1 = {
...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com']),
@@ -496,7 +483,7 @@ describe('Sidebar', () => {
CONST.BETAS.POLICY_ROOMS,
CONST.BETAS.POLICY_EXPENSE_CHAT,
];
-
+ const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks('0');
return waitForPromisesToResolve()
// When Onyx is updated with the data and the sidebar re-renders
@@ -504,7 +491,6 @@ describe('Sidebar', () => {
[ONYXKEYS.BETAS]: betas,
[ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT,
[ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
- [ONYXKEYS.CURRENTLY_VIEWED_REPORTID]: '0',
[`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1,
[`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2,
[`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3,
@@ -578,8 +564,6 @@ describe('Sidebar', () => {
});
it('puts archived chats last', () => {
- const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks();
-
// Given three unread reports, with the first report being archived
const report1 = {
...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com'], 3),
@@ -603,7 +587,7 @@ describe('Sidebar', () => {
CONST.BETAS.POLICY_ROOMS,
CONST.BETAS.POLICY_EXPENSE_CHAT,
];
-
+ const sidebarLinks = LHNTestUtils.getDefaultRenderedSidebarLinks('0');
return waitForPromisesToResolve()
// When Onyx is updated with the data and the sidebar re-renders
@@ -611,7 +595,6 @@ describe('Sidebar', () => {
[ONYXKEYS.BETAS]: betas,
[ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD,
[ONYXKEYS.PERSONAL_DETAILS]: LHNTestUtils.fakePersonalDetails,
- [ONYXKEYS.CURRENTLY_VIEWED_REPORTID]: '0',
[`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1,
[`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2,
[`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3,
diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js
index 89bfff2b5231..dce82f822c9b 100644
--- a/tests/unit/OptionsListUtilsTest.js
+++ b/tests/unit/OptionsListUtilsTest.js
@@ -283,8 +283,8 @@ describe('OptionsListUtils', () => {
// Then the 2 personalDetails that don't have reports should be returned
expect(results.personalDetails.length).toBe(2);
- // Then all of the reports should be shown, including the one that has no message on them.
- expect(results.recentReports.length).toBe(_.size(REPORTS));
+ // Then all of the reports should be shown EXCEPT the archived room, including the one that has no message on them.
+ expect(results.recentReports.length).toBe(_.size(REPORTS) - 1);
// When we filter again but provide a searchValue
results = OptionsListUtils.getSearchOptions(REPORTS, PERSONAL_DETAILS, 'spider');
diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js
index 9fd92655e7fd..53a1553aa951 100644
--- a/tests/unit/ReportUtilsTest.js
+++ b/tests/unit/ReportUtilsTest.js
@@ -4,6 +4,7 @@ import CONST from '../../src/CONST';
import ONYXKEYS from '../../src/ONYXKEYS';
import * as ReportUtils from '../../src/libs/ReportUtils';
import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
+import * as LHNTestUtils from '../utils/LHNTestUtils';
const currentUserEmail = 'bjorn@vikings.net';
const participantsPersonalDetails = {
@@ -223,4 +224,82 @@ describe('ReportUtils', () => {
});
});
});
+
+ describe('hasOutstandingIOU', () => {
+ it('returns false when there is no report', () => {
+ expect(ReportUtils.hasOutstandingIOU()).toBe(false);
+ });
+ it('returns false when the report has no iouReportID', () => {
+ const report = LHNTestUtils.getFakeReport();
+ expect(ReportUtils.hasOutstandingIOU(report)).toBe(false);
+ });
+ it('returns false when there is no iouReports collection', () => {
+ const report = {
+ ...LHNTestUtils.getFakeReport(),
+ iouReportID: '1',
+ };
+ expect(ReportUtils.hasOutstandingIOU(report)).toBe(false);
+ });
+ it('returns false when there is no matching IOU report', () => {
+ const report = {
+ ...LHNTestUtils.getFakeReport(),
+ iouReportID: '1',
+ };
+ const iouReports = {};
+ expect(ReportUtils.hasOutstandingIOU(report, undefined, iouReports)).toBe(false);
+ });
+ it('returns false when the matched IOU report does not have an owner email', () => {
+ const report = {
+ ...LHNTestUtils.getFakeReport(),
+ iouReportID: '1',
+ };
+ const iouReports = {
+ reportIOUs_1: {
+ reportID: '1',
+ },
+ };
+ expect(ReportUtils.hasOutstandingIOU(report, undefined, iouReports)).toBe(false);
+ });
+ it('returns false when the matched IOU report does not have an owner email', () => {
+ const report = {
+ ...LHNTestUtils.getFakeReport(),
+ iouReportID: '1',
+ };
+ const iouReports = {
+ reportIOUs_1: {
+ reportID: '1',
+ ownerEmail: 'a@a.com',
+ },
+ };
+ expect(ReportUtils.hasOutstandingIOU(report, 'b@b.com', iouReports)).toBe(false);
+ });
+ it('returns true when the report has an oustanding IOU', () => {
+ const report = {
+ ...LHNTestUtils.getFakeReport(),
+ iouReportID: '1',
+ hasOutstandingIOU: true,
+ };
+ const iouReports = {
+ reportIOUs_1: {
+ reportID: '1',
+ ownerEmail: 'a@a.com',
+ },
+ };
+ expect(ReportUtils.hasOutstandingIOU(report, 'b@b.com', iouReports)).toBe(true);
+ });
+ it('returns false when the report has no oustanding IOU', () => {
+ const report = {
+ ...LHNTestUtils.getFakeReport(),
+ iouReportID: '1',
+ hasOutstandingIOU: false,
+ };
+ const iouReports = {
+ reportIOUs_1: {
+ reportID: '1',
+ ownerEmail: 'a@a.com',
+ },
+ };
+ expect(ReportUtils.hasOutstandingIOU(report, 'b@b.com', iouReports)).toBe(false);
+ });
+ });
});
diff --git a/tests/utils/LHNTestUtils.js b/tests/utils/LHNTestUtils.js
index 49ee672f582d..540085fe1bf3 100644
--- a/tests/utils/LHNTestUtils.js
+++ b/tests/utils/LHNTestUtils.js
@@ -108,7 +108,11 @@ function getAdvancedFakeReport(hasComments, isArchived, isUserCreatedPolicyRoom,
};
}
-function getDefaultRenderedSidebarLinks() {
+/**
+ * @param {String} [reportIDFromRoute]
+ * @returns {RenderAPI}
+ */
+function getDefaultRenderedSidebarLinks(reportIDFromRoute = '') {
// An ErrorBoundary needs to be added to the rendering so that any errors that happen while the component
// renders are logged to the console. Without an error boundary, Jest only reports the error like "The above error
// occurred in your component", except, there is no "above error". It's just swallowed up by Jest somewhere.
@@ -148,6 +152,7 @@ function getDefaultRenderedSidebarLinks() {
}}
onAvatarClick={() => {}}
isSmallScreenWidth={false}
+ reportIDFromRoute={reportIDFromRoute}
/>