diff --git a/.github/actions/composite/setupNode/action.yml b/.github/actions/composite/setupNode/action.yml
index d475acf5380..643c707da23 100644
--- a/.github/actions/composite/setupNode/action.yml
+++ b/.github/actions/composite/setupNode/action.yml
@@ -12,6 +12,6 @@ runs:
- name: Install node packages
uses: nick-invision/retry@0711ba3d7808574133d713a0d92d2941be03a350
with:
- timeout_minutes: 10
- max_attempts: 5
+ timeout_minutes: 30
+ max_attempts: 3
command: npm ci
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 24cc348f295..453f315980f 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 1001029007
- versionName "1.2.90-7"
+ versionCode 1001029100
+ versionName "1.2.91-0"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
diff --git a/config/proxyConfig.js b/config/proxyConfig.js
new file mode 100644
index 00000000000..fa09c436461
--- /dev/null
+++ b/config/proxyConfig.js
@@ -0,0 +1,9 @@
+/**
+ * These are the base API roots used to send requests to the proxy.
+ * We only specify for staging URLs as API requests are sent to the production
+ * servers by default.
+ */
+module.exports = {
+ STAGING: '/staging/',
+ STAGING_SECURE: '/staging-secure/',
+};
diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js
index 0b48c4e5f35..2867d857ee0 100644
--- a/config/webpack/webpack.dev.js
+++ b/config/webpack/webpack.dev.js
@@ -21,6 +21,7 @@ module.exports = (env = {}) => portfinder.getPortPromise({port: BASE_PORT})
: {
proxy: {
'/api': 'http://[::1]:9000',
+ '/staging': 'http://[::1]:9000',
'/chat-attachments': 'http://[::1]:9000',
},
};
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html
index ac648bfe4fe..9c256602144 100644
--- a/docs/_layouts/default.html
+++ b/docs/_layouts/default.html
@@ -2,6 +2,7 @@
+
Expensify Help
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index fc94e2d70ba..3147a722dbc 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.2.90
+ 1.2.91
CFBundleSignature
????
CFBundleURLTypes
@@ -30,7 +30,7 @@
CFBundleVersion
- 1.2.90.7
+ 1.2.91.0
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index b01b54e8566..e22c6987255 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.2.90
+ 1.2.91
CFBundleSignature
????
CFBundleVersion
- 1.2.90.7
+ 1.2.91.0
diff --git a/package-lock.json b/package-lock.json
index 2e8d67ca153..254909a73aa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.2.90-7",
+ "version": "1.2.91-0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.2.90-7",
+ "version": "1.2.91-0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -135,7 +135,7 @@
"css-loader": "^6.7.2",
"diff-so-fancy": "^1.3.0",
"dotenv": "^16.0.3",
- "electron": "^22.3.4",
+ "electron": "22.3.4",
"electron-builder": "23.5.0",
"electron-notarize": "^1.2.1",
"eslint": "^7.6.0",
diff --git a/package.json b/package.json
index 58dc1196b3b..03eea0cc0fe 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.2.90-7",
+ "version": "1.2.91-0",
"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/CONFIG.js b/src/CONFIG.js
index 6b8d191b514..d430f9d9883 100644
--- a/src/CONFIG.js
+++ b/src/CONFIG.js
@@ -84,4 +84,5 @@ export default {
DEV_PORT: process.env.PORT || 8080,
E2E_TESTING: lodashGet(Config, 'E2E_TESTING', 'false') === 'true',
SEND_CRASH_REPORTS: lodashGet(Config, 'SEND_CRASH_REPORTS', 'false') === 'true',
+ IS_USING_WEB_PROXY: getPlatform() === 'web' && useWebProxy,
};
diff --git a/src/CONST.js b/src/CONST.js
index 0e19417f596..027be1c1aac 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -337,6 +337,48 @@ const CONST = {
IOU: 'IOU',
RENAMED: 'RENAMED',
CHRONOSOOOLIST: 'CHRONOSOOOLIST',
+ POLICYCHANGELOG: {
+ UPDATE_NAME: 'POLICYCHANGELOG_UPDATE_NAME',
+ UPDATE_CURRENCY: 'POLICYCHANGELOG_UPDATE_CURRENCY',
+ UPDATE_OWNERSHIP: 'POLICYCHANGELOG_UPDATE_OWNERSHIP',
+ UPDATE_AUTOHARVESTING: 'POLICYCHANGELOG_UPDATE_AUTOHARVESTING',
+ UPDATE_AUTOREPORTING_FREQUENCY: 'POLICYCHANGELOG_UPDATE_AUTOREPORTING_FREQUENCY',
+ UPDATE_DEFAULT_TITLE_ENFORCED: 'POLICYCHANGELOG_UPDATE_DEFAULT_TITLE_ENFORCED',
+ UPDATE_REPORT_FIELD: 'POLICYCHANGELOG_UPDATE_REPORT_FIELD',
+ ADD_REPORT_FIELD: 'POLICYCHANGELOG_ADD_REPORT_FIELD',
+ DELETE_REPORT_FIELD: 'POLICYCHANGELOG_DELETE_REPORT_FIELD',
+ UPDATE_DEFAULT_TITLE: 'POLICYCHANGELOG_UPDATE_DEFAULT_TITLE',
+ ADD_CATEGORY: 'POLICYCHANGELOG_ADD_CATEGORY',
+ DELETE_CATEGORY: 'POLICYCHANGELOG_DELETE_CATEGORY',
+ SET_CATEGORY_NAME: 'POLICYCHANGELOG_SET_CATEGORY_NAME',
+ UPDATE_CATEGORY: 'POLICYCHANGELOG_UPDATE_CATEGORY',
+ ADD_TAG: 'POLICYCHANGELOG_ADD_TAG',
+ UPDATE_TAG: 'POLICYCHANGELOG_UPDATE_TAG',
+ DELETE_TAG: 'POLICYCHANGELOG_DELETE_TAG',
+ UPDATE_TAG_NAME: 'POLICYCHANGELOG_UPDATE_TAG_NAME',
+ UPDATE_TAG_LIST_NAME: 'POLICYCHANGELOG_UPDATE_TAG_LIST_NAME',
+ IMPORT_TAGS: 'POLICYCHANGELOG_IMPORT_TAGS',
+ DELETE_ALL_TAGS: 'POLICYCHANGELOG_DELETE_ALL_TAGS',
+ ADD_APPROVER_RULE: 'POLICYCHANGELOG_ADD_APPROVER_RULE',
+ UPDATE_APPROVER_RULE: 'POLICYCHANGELOG_UPDATE_APPROVER_RULE',
+ DELETE_APPROVER_RULE: 'POLICYCHANGELOG_DELETE_APPROVER_RULE',
+ ADD_EMPLOYEE: 'POLICYCHANGELOG_ADD_EMPLOYEE',
+ DELETE_EMPLOYEE: 'POLICYCHANGELOG_DELETE_EMPLOYEE',
+ UPDATE_EMPLOYEE: 'POLICYCHANGELOG_UPDATE_EMPLOYEE',
+ SET_AUTO_JOIN: 'POLICYCHANGELOG_SET_AUTO_JOIN',
+ ADD_INTEGRATION: 'POLICYCHANGELOG_ADD_INTEGRATION',
+ DELETE_INTEGRATION: 'POLICYCHANGELOG_DELETE_INTEGRATION',
+ UPDATE_ACH_ACCOUNT: 'POLICYCHANGELOG_UPDATE_ACH_ACCOUNT',
+ UPDATE_REIMBURSEMENT_CHOICE: 'POLICYCHANGELOG_UPDATE_REIMBURSEMENT_CHOICE',
+ SET_AUTOREIMBURSEMENT: 'POLICYCHANGELOG_SET_AUTOREIMBURSEMENT',
+ ADD_CUSTOM_UNIT: 'POLICYCHANGELOG_ADD_CUSTOM_UNIT',
+ DELETE_CUSTOM_UNIT: 'POLICYCHANGELOG_DELETE_CUSTOM_UNIT',
+ UPDATE_CUSTOM_UNIT: 'POLICYCHANGELOG_UPDATE_CUSTOM_UNIT',
+ UPDATE_CUSTOM_UNIT_RATE: 'POLICYCHANGELOG_UPDATE_CUSTOM_UNIT_RATE',
+ ADD_CUSTOM_UNIT_RATE: 'POLICYCHANGELOG_ADD_CUSTOM_UNIT_RATE',
+ DELETE_CUSTOM_UNIT_RATE: 'POLICYCHANGELOG_DELETE_CUSTOM_UNIT_RATE',
+ UPDATE_FIELD: 'POLICYCHANGELOG_UPDATE_FIELD',
+ },
},
},
ARCHIVE_REASON: {
@@ -606,6 +648,10 @@ const CONST = {
WIDTH: 320,
HEIGHT: 416,
},
+ CATEGORY_SHORTCUT_BAR_HEIGHT: 40,
+ SMALL_EMOJI_PICKER_SIZE: {
+ WIDTH: '100%',
+ },
NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT: 256,
EMOJI_PICKER_ITEM_HEIGHT: 32,
EMOJI_PICKER_HEADER_HEIGHT: 32,
diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js
index 7e22542c048..9cb4825fdbf 100644
--- a/src/components/AddressSearch.js
+++ b/src/components/AddressSearch.js
@@ -128,12 +128,14 @@ const AddressSearch = (props) => {
administrative_area_level_1: 'long_name',
});
+ // Make sure that the order of keys remains such that the country is always set above the state.
+ // Refer to https://github.com/Expensify/App/issues/15633 for more information.
const values = {
street: props.value ? props.value.trim() : '',
city: city || cityFallback,
zipCode,
- state,
country: '',
+ state,
};
// If the address is not in the US, use the full length state name since we're displaying the address's
diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js
index 86e9e9690a9..87835062a95 100644
--- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js
+++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js
@@ -22,7 +22,9 @@ const propTypes = {
onPressOut: PropTypes.func,
/** If a file download is happening */
- download: PropTypes.bool,
+ download: PropTypes.shape({
+ isDownloading: PropTypes.bool.isRequired,
+ }),
...anchorForAttachmentsOnlyPropTypes,
};
@@ -30,7 +32,7 @@ const propTypes = {
const defaultProps = {
onPressIn: undefined,
onPressOut: undefined,
- download: false,
+ download: {isDownloading: false},
...anchorForAttachmentsOnlyDefaultProps,
};
diff --git a/src/components/AttachmentCarousel/index.js b/src/components/AttachmentCarousel/index.js
index 144d0aaa087..bd232526bd9 100644
--- a/src/components/AttachmentCarousel/index.js
+++ b/src/components/AttachmentCarousel/index.js
@@ -196,6 +196,7 @@ class AttachmentCarousel extends React.Component {
this.toggleArrowsVisibility(!this.state.shouldShowArrow)}
source={authSource}
+ key={authSource}
file={this.state.file}
/>
diff --git a/src/components/EmojiPicker/EmojiPicker.js b/src/components/EmojiPicker/EmojiPicker/index.js
similarity index 90%
rename from src/components/EmojiPicker/EmojiPicker.js
rename to src/components/EmojiPicker/EmojiPicker/index.js
index 369365a8db8..5238c2dbdb1 100644
--- a/src/components/EmojiPicker/EmojiPicker.js
+++ b/src/components/EmojiPicker/EmojiPicker/index.js
@@ -1,9 +1,17 @@
import React from 'react';
import {Dimensions, Keyboard} from 'react-native';
import _ from 'underscore';
-import EmojiPickerMenu from './EmojiPickerMenu';
-import CONST from '../../CONST';
-import PopoverWithMeasuredContent from '../PopoverWithMeasuredContent';
+import EmojiPickerMenu from '../EmojiPickerMenu';
+import CONST from '../../../CONST';
+import PopoverWithMeasuredContent from '../../PopoverWithMeasuredContent';
+import compose from '../../../libs/compose';
+import withViewportOffsetTop, {viewportOffsetTopPropTypes} from '../../withViewportOffsetTop';
+import withWindowDimensions, {windowDimensionsPropTypes} from '../../withWindowDimensions';
+
+const propTypes = {
+ ...viewportOffsetTopPropTypes,
+ ...windowDimensionsPropTypes,
+};
const DEFAULT_ANCHOR_ORIGIN = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
@@ -176,6 +184,7 @@ class EmojiPicker extends React.Component {
}}
anchorOrigin={this.state.emojiPopoverAnchorOrigin}
measureContent={this.measureContent}
+ outerStyle={{maxHeight: this.props.windowHeight, marginTop: this.props.viewportOffsetTop}}
>
- {!this.props.isSmallScreenWidth && (
-
- this.searchInput = el}
- autoFocus
- selectTextOnFocus={this.state.selectTextOnFocus}
- onSelectionChange={this.onSelectionChange}
- onFocus={() => this.setState({isFocused: true, highlightedIndex: -1, isUsingKeyboardMovement: false})}
- onBlur={() => this.setState({isFocused: false})}
- />
-
- )}
+
+ this.searchInput = el}
+ autoFocus={!this.isMobileLandscape() || this.props.isSmallScreenWidth}
+ selectTextOnFocus={this.state.selectTextOnFocus}
+ onSelectionChange={this.onSelectionChange}
+ onFocus={() => this.setState({isFocused: true, highlightedIndex: -1, isUsingKeyboardMovement: false})}
+ onBlur={() => this.setState({isFocused: false})}
+ autoCorrect={false}
+ />
+
{!isFiltered && (
this.emojiList = el}
data={this.state.filteredEmojis}
renderItem={this.renderItem}
- keyExtractor={item => `emoji_picker_${item.code}`}
+ keyExtractor={this.keyExtractor}
numColumns={CONST.EMOJI_NUM_PER_ROW}
style={[
styles.emojiPickerList,
+ StyleUtils.getEmojiPickerListHeight(isFiltered),
this.isMobileLandscape() && styles.emojiPickerListLandscape,
]}
extraData={
diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js
index 702be109e5e..149e180c7cd 100644
--- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js
+++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js
@@ -16,7 +16,9 @@ import withLocalize, {withLocalizePropTypes} from '../../withLocalize';
import EmojiSkinToneList from '../EmojiSkinToneList';
import * as EmojiUtils from '../../../libs/EmojiUtils';
import * as User from '../../../libs/actions/User';
+import TextInput from '../../TextInput';
import CategoryShortcutBar from '../CategoryShortcutBar';
+import * as StyleUtils from '../../../styles/StyleUtils';
const propTypes = {
/** Function to add the selected emoji to the main compose text input */
@@ -64,14 +66,48 @@ class EmojiPickerMenu extends Component {
this.renderItem = this.renderItem.bind(this);
this.isMobileLandscape = this.isMobileLandscape.bind(this);
this.updatePreferredSkinTone = this.updatePreferredSkinTone.bind(this);
+ this.filterEmojis = _.debounce(this.filterEmojis.bind(this), 300);
this.scrollToHeader = this.scrollToHeader.bind(this);
this.getItemLayout = this.getItemLayout.bind(this);
+
+ this.state = {
+ filteredEmojis: this.emojis,
+ headerIndices: this.headerRowIndices,
+ };
}
getItemLayout(data, index) {
return {length: CONST.EMOJI_PICKER_ITEM_HEIGHT, offset: CONST.EMOJI_PICKER_ITEM_HEIGHT * index, index};
}
+ /**
+ * Filter the entire list of emojis to only emojis that have the search term in their keywords
+ *
+ * @param {String} searchTerm
+ */
+ filterEmojis(searchTerm) {
+ const normalizedSearchTerm = searchTerm.toLowerCase().trim();
+
+ if (this.emojiList) {
+ this.emojiList.scrollToOffset({offset: 0, animated: false});
+ }
+
+ if (normalizedSearchTerm === '') {
+ this.setState({
+ filteredEmojis: this.emojis,
+ headerIndices: this.headerRowIndices,
+ });
+
+ return;
+ }
+ const newFilteredEmojiList = EmojiUtils.suggestEmojis(`:${normalizedSearchTerm}`, this.emojis.length);
+
+ this.setState({
+ filteredEmojis: newFilteredEmojiList,
+ headerIndices: undefined,
+ });
+ }
+
/**
* @param {String} emoji
* @param {Object} emojiObject
@@ -112,6 +148,16 @@ class EmojiPickerMenu extends Component {
})();
}
+ /**
+ * Return a unique key for each emoji item
+ *
+ * @param {Object} item
+ * @returns {String}
+ */
+ keyExtractor(item) {
+ return (`emoji_picker_${item.code}`);
+ }
+
/**
* Given an emoji item object, render a component based on its type.
* Items with the code "SPACER" return nothing and are used to fill rows up to 8
@@ -149,28 +195,56 @@ class EmojiPickerMenu extends Component {
}
render() {
+ const isFiltered = this.emojis.length !== this.state.filteredEmojis.length;
return (
-
+
+
+
+ {!isFiltered && (
-
- this.emojiList = el}
- data={this.emojis}
- renderItem={this.renderItem}
- keyExtractor={item => (`emoji_picker_${item.code}`)}
- numColumns={CONST.EMOJI_NUM_PER_ROW}
- style={[
- styles.emojiPickerList,
- this.isMobileLandscape() && styles.emojiPickerListLandscape,
- ]}
- stickyHeaderIndices={this.headerRowIndices}
- getItemLayout={this.getItemLayout}
- showsVerticalScrollIndicator
- />
+ )}
+ {this.state.filteredEmojis.length === 0
+ ? (
+
+
+ {this.props.translate('common.noResultsFound')}
+
+
+ )
+ : (
+ this.emojiList = el}
+ keyboardShouldPersistTaps="handled"
+ data={this.state.filteredEmojis}
+ renderItem={this.renderItem}
+ keyExtractor={this.keyExtractor}
+ numColumns={CONST.EMOJI_NUM_PER_ROW}
+ style={[
+ styles.emojiPickerList,
+ StyleUtils.getEmojiPickerListHeight(isFiltered),
+ this.isMobileLandscape() && styles.emojiPickerListLandscape,
+ ]}
+ stickyHeaderIndices={this.state.headerIndices}
+ getItemLayout={this.getItemLayout}
+ showsVerticalScrollIndicator
+
+ // used because of a bug in RN where stickyHeaderIndices can't be updated after the list is rendered https://github.com/facebook/react-native/issues/25157
+ removeClippedSubviews={false}
+ />
+ )}
{
this.inputRefs[inputID] = node;
- // Call the original ref, if any
const {ref} = child;
if (_.isFunction(ref)) {
ref(node);
@@ -285,7 +284,7 @@ class Form extends React.Component {
},
value: this.state.inputValues[inputID],
errorText: this.state.errors[inputID] || fieldErrorMessage,
- onBlur: () => {
+ onBlur: (event) => {
// We delay the validation in order to prevent Checkbox loss of focus when
// the user are focusing a TextInput and proceeds to toggle a CheckBox in
// web and mobile web platforms.
@@ -293,6 +292,10 @@ class Form extends React.Component {
this.setTouchedInput(inputID);
this.validate(this.state.inputValues);
}, 200);
+
+ if (_.isFunction(child.props.onBlur)) {
+ child.props.onBlur(event);
+ }
},
onInputChange: (value, key) => {
const inputKey = key || inputID;
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js
index 8c27cba1f7a..be8c8a1fc3e 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js
@@ -16,6 +16,7 @@ import AnchorForCommentsOnly from '../../AnchorForCommentsOnly';
import AnchorForAttachmentsOnly from '../../AnchorForAttachmentsOnly';
import * as Url from '../../../libs/Url';
import ROUTES from '../../../ROUTES';
+import tryResolveUrlFromApiRoot from '../../../libs/tryResolveUrlFromApiRoot';
const AnchorRenderer = (props) => {
const htmlAttribs = props.tnode.attributes;
@@ -77,7 +78,7 @@ const AnchorRenderer = (props) => {
if (isAttachment) {
return (
);
diff --git a/src/components/Hoverable/index.js b/src/components/Hoverable/index.js
index 2f10cca0c1e..12145b93b26 100644
--- a/src/components/Hoverable/index.js
+++ b/src/components/Hoverable/index.js
@@ -69,19 +69,15 @@ class Hoverable extends Component {
onMouseEnter: (el) => {
this.setIsHovered(true);
- // Call the original onMouseEnter, if any
- const {onMouseEnter} = this.props.children;
- if (_.isFunction(onMouseEnter)) {
- onMouseEnter(el);
+ if (_.isFunction(this.props.children.props.onMouseEnter)) {
+ this.props.children.props.onMouseEnter(el);
}
},
onMouseLeave: (el) => {
this.setIsHovered(false);
- // Call the original onMouseLeave, if any
- const {onMouseLeave} = this.props.children;
- if (_.isFunction(onMouseLeave)) {
- onMouseLeave(el);
+ if (_.isFunction(this.props.children.props.onMouseLeave)) {
+ this.props.children.props.onMouseLeave(el);
}
},
onBlur: (el) => {
@@ -89,10 +85,8 @@ class Hoverable extends Component {
this.setIsHovered(false);
}
- // Call the original onBlur, if any
- const {onBlur} = this.props.children;
- if (_.isFunction(onBlur)) {
- onBlur(el);
+ if (_.isFunction(this.props.children.props.onBlur)) {
+ this.props.children.props.onBlur(el);
}
},
});
diff --git a/src/components/Icon/index.js b/src/components/Icon/index.js
index c65030afc40..269f642d8ed 100644
--- a/src/components/Icon/index.js
+++ b/src/components/Icon/index.js
@@ -52,7 +52,7 @@ class Icon extends PureComponent {
if (this.props.inline) {
return (
@@ -68,7 +68,7 @@ class Icon extends PureComponent {
return (
{
+ if (props.shouldApplyToAndroid) {
+ return (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+ );
+ }
+ const viewProps = _.omit(props, ['behavior', 'contentContainerStyle', 'enabled', 'keyboardVerticalOffset']);
+ return (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+ );
+};
+
+KeyboardAvoidingView.displayName = 'KeyboardAvoidingView';
+KeyboardAvoidingView.propTypes = propTypes;
+KeyboardAvoidingView.defaultProps = defaultProps;
+
+export default KeyboardAvoidingView;
diff --git a/src/components/KeyboardAvoidingView/index.ios.js b/src/components/KeyboardAvoidingView/index.ios.js
index aeeb32e417b..58f40b3276a 100644
--- a/src/components/KeyboardAvoidingView/index.ios.js
+++ b/src/components/KeyboardAvoidingView/index.ios.js
@@ -1,6 +1,3 @@
-/*
- * The KeyboardAvoidingView is only used on ios
- */
import React from 'react';
import {KeyboardAvoidingView as KeyboardAvoidingViewComponent} from 'react-native';
diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js
index a07b95a3675..c8f9e36f21b 100644
--- a/src/components/LHNOptionsList/OptionRowLHN.js
+++ b/src/components/LHNOptionsList/OptionRowLHN.js
@@ -127,7 +127,7 @@ const OptionRowLHN = (props) => {
hovered && !props.isFocused ? props.hoverStyle : null,
]}
>
-
+
{
{
{optionItem.isChatRoom && (
)}
@@ -188,7 +188,7 @@ const OptionRowLHN = (props) => {
{optionItem.alternateText}
diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js
index 1e314654bba..26018641d6c 100644
--- a/src/components/Modal/BaseModal.js
+++ b/src/components/Modal/BaseModal.js
@@ -10,6 +10,7 @@ import {propTypes as modalPropTypes, defaultProps as modalDefaultProps} from './
import * as Modal from '../../libs/actions/Modal';
import getModalStyles from '../../styles/getModalStyles';
import variables from '../../styles/variables';
+import KeyboardAvoidingView from '../KeyboardAvoidingView';
const propTypes = {
...modalPropTypes,
@@ -132,6 +133,7 @@ class BaseModal extends PureComponent {
animationInTiming={this.props.animationInTiming}
animationOutTiming={this.props.animationOutTiming}
statusBarTranslucent={this.props.statusBarTranslucent}
+ avoidKeyboard={this.props.avoidKeyboard}
>
{(insets) => {
@@ -156,8 +158,7 @@ class BaseModal extends PureComponent {
modalContainerStylePaddingTop: modalContainerStyle.paddingTop,
modalContainerStylePaddingBottom: modalContainerStyle.paddingBottom,
});
-
- return (
+ const content = (
);
+
+ return (
+
+ {content}
+
+ );
}}
diff --git a/src/components/OptionRow.js b/src/components/OptionRow.js
index 29c2e3c5d0c..8c08765b569 100644
--- a/src/components/OptionRow.js
+++ b/src/components/OptionRow.js
@@ -232,7 +232,7 @@ class OptionRow extends Component {
}
{
};
return (
-
+
{
ref={ref}
onPress={openEmojiPicker}
isDelayButtonStateComplete={false}
- tooltipText={props.translate('reportActionContextMenu.addReactionTooltip')}
+ tooltipText={props.translate('emojiReactions.addReactionTooltip')}
>
{({hovered, pressed}) => (
{
styles.mt1,
styles.textMicroBold,
styles.textReactionSenders,
+ styles.textAlignCenter,
]}
>
{namesString}
@@ -62,7 +63,7 @@ const ReactionTooltipContent = (props) => {
styles.fontColorReactionLabel,
]}
>
- {`reacted with :${props.emojiName}:`}
+ {`${props.translate('emojiReactions.reactedWith')} :${props.emojiName}:`}
);
diff --git a/src/components/ReportActionItem/IOUQuote.js b/src/components/ReportActionItem/IOUQuote.js
index 032297505a7..f93888d4144 100644
--- a/src/components/ReportActionItem/IOUQuote.js
+++ b/src/components/ReportActionItem/IOUQuote.js
@@ -2,7 +2,6 @@ import React from 'react';
import {View, Pressable} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
-import Str from 'expensify-common/lib/str';
import Text from '../Text';
import Icon from '../Icon';
import * as Expensicons from '../Icon/Expensicons';
@@ -76,14 +75,14 @@ const IOUQuote = props => (
{/* Get first word of IOU message */}
- {Str.htmlDecode(fragment.text.split(' ')[0])}
+ {fragment.text.split(' ')[0]}
{/* Get remainder of IOU message */}
- {Str.htmlDecode(fragment.text.substring(fragment.text.indexOf(' ')))}
+ {fragment.text.substring(fragment.text.indexOf(' '))}
diff --git a/src/components/ReportTransaction.js b/src/components/ReportTransaction.js
index 5586f5e67c2..be53b11c742 100644
--- a/src/components/ReportTransaction.js
+++ b/src/components/ReportTransaction.js
@@ -1,7 +1,6 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
-import Str from 'expensify-common/lib/str';
import styles from '../styles/styles';
import CONST from '../CONST';
import * as IOU from '../libs/actions/IOU';
@@ -77,7 +76,7 @@ class ReportTransaction extends Component {
wrapperStyles={[styles.reportTransactionWrapper]}
>
- {Str.htmlDecode(this.props.action.message[0].html)}
+ {this.props.action.message[0].text}
{this.props.canBeRejected && (
diff --git a/src/components/TestToolMenu.js b/src/components/TestToolMenu.js
index c1512a805bf..eaae9aec984 100644
--- a/src/components/TestToolMenu.js
+++ b/src/components/TestToolMenu.js
@@ -15,6 +15,7 @@ import networkPropTypes from './networkPropTypes';
import compose from '../libs/compose';
import {withNetwork} from './OnyxProvider';
import * as ApiUtils from '../libs/ApiUtils';
+import CONFIG from '../CONFIG';
const propTypes = {
/** User object in Onyx */
@@ -42,15 +43,18 @@ const TestToolMenu = props => (
{/* Option to switch between staging and default api endpoints.
- This enables QA and internal testers to take advantage of sandbox environments for 3rd party services like Plaid and Onfido. */}
-
- User.setShouldUseStagingServer(
- !lodashGet(props, 'user.shouldUseStagingServer', ApiUtils.isUsingStagingApi()),
- )}
- />
-
+ This enables QA, internal testers and external devs to take advantage of sandbox environments for 3rd party services like Plaid and Onfido.
+ This toggle is not rendered for internal devs as they make environment changes directly to the .env file. */}
+ {!CONFIG.IS_USING_LOCAL_WEB && (
+
+ User.setShouldUseStagingServer(
+ !lodashGet(props, 'user.shouldUseStagingServer', ApiUtils.isUsingStagingApi()),
+ )}
+ />
+
+ )}
{/* When toggled the app will be forced offline. */}
diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js
index 104098b375f..b84fbd6cb0e 100644
--- a/src/components/Tooltip/index.js
+++ b/src/components/Tooltip/index.js
@@ -172,10 +172,8 @@ class Tooltip extends PureComponent {
onBlur: (el) => {
this.hideTooltip();
- // Call the original onBlur, if any
- const {onBlur} = this.props.children;
- if (_.isFunction(onBlur)) {
- onBlur(el);
+ if (_.isFunction(this.props.children.props.onBlur)) {
+ this.props.children.props.onBlur(el);
}
},
focusable: true,
diff --git a/src/components/UnreadActionIndicator.js b/src/components/UnreadActionIndicator.js
index 795edcaac7d..b5124155b6b 100755
--- a/src/components/UnreadActionIndicator.js
+++ b/src/components/UnreadActionIndicator.js
@@ -5,7 +5,7 @@ import Text from './Text';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
const UnreadActionIndicator = props => (
-
+
{props.translate('common.new')}
diff --git a/src/languages/en.js b/src/languages/en.js
index 765be98ed4d..225dc35af0a 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -231,7 +231,10 @@ export default {
editComment: 'Edit comment',
deleteComment: 'Delete comment',
deleteConfirmation: 'Are you sure you want to delete this comment?',
+ },
+ emojiReactions: {
addReactionTooltip: 'Add reaction',
+ reactedWith: 'reacted with',
},
reportActionsView: {
beginningOfArchivedRoomPartOne: 'You missed the party in ',
@@ -244,7 +247,7 @@ export default {
beginningOfChatHistoryAnnounceRoomPartTwo: ({workspaceName}) => ` to chat about anything ${workspaceName} related.`,
beginningOfChatHistoryUserRoomPartOne: 'Collaboration starts here! 🎉\nUse this space to chat about anything ',
beginningOfChatHistoryUserRoomPartTwo: ' related.',
- beginningOfChatHistory: 'This is the beginning of your chat history with ',
+ beginningOfChatHistory: 'This is the beginning of your chat with ',
beginningOfChatHistoryPolicyExpenseChatPartOne: 'Collaboration between ',
beginningOfChatHistoryPolicyExpenseChatPartTwo: ' and ',
beginningOfChatHistoryPolicyExpenseChatPartThree: ' starts here! 🎉 This is the place to chat, request money and settle up.',
@@ -276,6 +279,8 @@ export default {
fabNewChat: 'New chat (Floating action)',
chatPinned: 'Chat pinned',
draftedMessage: 'Drafted message',
+ listOfChatMessages: 'List of chat messages',
+ listOfChats: 'List of chats',
},
iou: {
amount: 'Amount',
@@ -345,6 +350,7 @@ export default {
pronounsPage: {
pronouns: 'Pronouns',
isShownOnProfile: 'Your pronouns are shown on your profile.',
+ placeholderText: 'Search to see options',
},
contacts: {
contactMethod: 'Contact method',
@@ -608,6 +614,7 @@ export default {
invalidFormatEmailLogin: 'The email entered is invalid. Please fix the format and try again.',
},
cannotGetAccountDetails: 'Couldn\'t retrieve account details, please try to sign in again.',
+ loginForm: 'Login form',
},
personalDetails: {
error: {
@@ -1167,6 +1174,7 @@ export default {
},
report: {
genericAddCommentFailureMessage: 'Unexpected error while posting the comment, please try again later',
+ noActivityYet: 'No activity yet',
},
chronos: {
oooEventSummaryFullDay: ({summary, dayCount, date}) => `${summary} for ${dayCount} ${dayCount === 1 ? 'day' : 'days'} until ${date}`,
@@ -1203,4 +1211,15 @@ export default {
},
allStates: COMMON_CONST.STATES,
allCountries: CONST.ALL_COUNTRIES,
+ accessibilityHints: {
+ navigateToChatsList: 'Navigate back to chats list',
+ chatWelcomeMessage: 'Chat welcome message',
+ navigatesToChat: 'Navigates to a chat',
+ newMessageLineIndicator: 'New message line indicator',
+ chatMessage: 'Chat message',
+ lastChatMessagePreview: 'Last chat message preview',
+ workspaceName: 'Workspace name',
+ chatUserDisplayNames: 'Chat user display names',
+ scrollToNewestMessages: 'Scroll to newest messages',
+ },
};
diff --git a/src/languages/es.js b/src/languages/es.js
index f7012c3d665..3a6eb53ae40 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -230,7 +230,10 @@ export default {
editComment: 'Editar comentario',
deleteComment: 'Eliminar comentario',
deleteConfirmation: '¿Estás seguro de que quieres eliminar este comentario?',
+ },
+ emojiReactions: {
addReactionTooltip: 'Añadir una reacción',
+ reactedWith: 'reaccionó con',
},
reportActionsView: {
beginningOfArchivedRoomPartOne: 'Te perdiste la fiesta en ',
@@ -243,7 +246,7 @@ export default {
beginningOfChatHistoryAnnounceRoomPartTwo: ({workspaceName}) => ` para chatear sobre cualquier cosa relacionada con ${workspaceName}.`,
beginningOfChatHistoryUserRoomPartOne: 'Este es el lugar para colaborar! 🎉\nUsa este espacio para chatear sobre cualquier cosa relacionada con ',
beginningOfChatHistoryUserRoomPartTwo: '.',
- beginningOfChatHistory: 'Aquà comienza tu historial de conversaciones con ',
+ beginningOfChatHistory: 'Aquà comienzan tus conversaciones con ',
beginningOfChatHistoryPolicyExpenseChatPartOne: '¡La colaboración entre ',
beginningOfChatHistoryPolicyExpenseChatPartTwo: ' y ',
beginningOfChatHistoryPolicyExpenseChatPartThree: ' empieza aquÃ! 🎉 Este es el lugar donde chatear, pedir dinero y pagar.',
@@ -275,6 +278,8 @@ export default {
fabNewChat: 'Nuevo chat',
chatPinned: 'Chat fijado',
draftedMessage: 'Mensaje borrador',
+ listOfChatMessages: 'Lista de mensajes del chat',
+ listOfChats: 'lista de chats',
},
iou: {
amount: 'Importe',
@@ -344,6 +349,7 @@ export default {
pronounsPage: {
pronouns: 'Pronombres',
isShownOnProfile: 'Tus pronombres se muestran en tu perfil.',
+ placeholderText: 'Buscar para ver opciones',
},
contacts: {
contactMethod: 'Método de contacto',
@@ -607,6 +613,7 @@ export default {
invalidFormatEmailLogin: 'El email introducido no es válido. CorrÃgelo e inténtalo de nuevo.',
},
cannotGetAccountDetails: 'No se pudieron cargar los detalles de tu cuenta, por favor intenta iniciar sesión de nuevo.',
+ loginForm: 'Formulario de inicio de sesión',
},
personalDetails: {
error: {
@@ -1168,6 +1175,7 @@ export default {
},
report: {
genericAddCommentFailureMessage: 'Error inesperado al agregar el comentario, por favor inténtalo más tarde',
+ noActivityYet: 'Sin actividad todavÃa',
},
chronos: {
oooEventSummaryFullDay: ({summary, dayCount, date}) => `${summary} por ${dayCount} ${dayCount === 1 ? 'dÃa' : 'dÃas'} hasta el ${date}`,
@@ -1669,4 +1677,15 @@ export default {
ZM: 'Zambia',
ZW: 'Zimbabue',
},
+ accessibilityHints: {
+ navigateToChatsList: 'Vuelve a la lista de chats',
+ chatWelcomeMessage: 'Mensaje de bienvenida al chat',
+ navigatesToChat: 'Navega a un chat',
+ newMessageLineIndicator: 'Indicador de nueva lÃnea de mensaje',
+ chatMessage: 'mensaje de chat',
+ lastChatMessagePreview: 'Vista previa del último mensaje del chat',
+ workspaceName: 'Nombre del espacio de trabajo',
+ chatUserDisplayNames: 'Nombres de los usuarios del chat',
+ scrollToNewestMessages: 'Desplázate a los mensajes más recientes',
+ },
};
diff --git a/src/libs/ApiUtils.js b/src/libs/ApiUtils.js
index 7d779248090..08533c83b07 100644
--- a/src/libs/ApiUtils.js
+++ b/src/libs/ApiUtils.js
@@ -4,6 +4,7 @@ import ONYXKEYS from '../ONYXKEYS';
import CONFIG from '../CONFIG';
import CONST from '../CONST';
import * as Environment from './Environment/Environment';
+import proxyConfig from '../../config/proxyConfig';
// To avoid rebuilding native apps, native apps use production config for both staging and prod
// We use the async environment check because it works on all platforms
@@ -17,8 +18,8 @@ Environment.getEnvironment()
Onyx.connect({
key: ONYXKEYS.USER,
callback: (val) => {
- // Toggling between APIs is not allowed on production
- if (ENV_NAME === CONST.ENVIRONMENT.PRODUCTION) {
+ // Toggling between APIs is not allowed on production and internal dev environment
+ if (ENV_NAME === CONST.ENVIRONMENT.PRODUCTION || CONFIG.IS_USING_LOCAL_WEB) {
shouldUseStagingServer = false;
return;
}
@@ -41,6 +42,11 @@ function getApiRoot(request) {
const shouldUseSecure = lodashGet(request, 'shouldUseSecure', false);
if (shouldUseStagingServer) {
+ if (CONFIG.IS_USING_WEB_PROXY) {
+ return shouldUseSecure
+ ? proxyConfig.STAGING_SECURE
+ : proxyConfig.STAGING;
+ }
return shouldUseSecure
? CONFIG.EXPENSIFY.STAGING_SECURE_API_ROOT
: CONFIG.EXPENSIFY.STAGING_API_ROOT;
diff --git a/src/libs/Notification/LocalNotification/BrowserNotifications.js b/src/libs/Notification/LocalNotification/BrowserNotifications.js
index 8b00870312c..170f05aacf7 100644
--- a/src/libs/Notification/LocalNotification/BrowserNotifications.js
+++ b/src/libs/Notification/LocalNotification/BrowserNotifications.js
@@ -1,6 +1,5 @@
// Web and desktop implementation only. Do not import for direct use. Use LocalNotification.
import _ from 'underscore';
-import Str from 'expensify-common/lib/str';
import focusApp from './focusApp';
import * as AppUpdate from '../../actions/AppUpdate';
import EXPENSIFY_ICON_URL from '../../../../assets/images/expensify-logo-round-clearspace.png';
@@ -107,10 +106,10 @@ export default {
*/
pushReportCommentNotification({reportAction, onClick}, usesIcon = false) {
const {person, message} = reportAction;
- const plainTextPerson = Str.htmlDecode(_.map(person, f => f.text).join());
+ const plainTextPerson = _.map(person, f => f.text).join();
// Specifically target the comment part of the message
- const plainTextMessage = Str.htmlDecode((_.find(message, f => f.type === 'COMMENT') || {}).text);
+ const plainTextMessage = (_.find(message, f => f.type === 'COMMENT') || {}).text;
push({
title: plainTextPerson,
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index 2b91b20462a..35eb00229b7 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -328,7 +328,7 @@ function createOption(logins, personalDetails, report, reportActions = {}, {
if (ReportUtils.isReportMessageAttachment({text: report.lastMessageText, html: report.lastMessageHtml})) {
lastMessageTextFromReport = `[${Localize.translateLocal('common.attachment')}]`;
} else {
- lastMessageTextFromReport = Str.htmlDecode(report ? report.lastMessageText : '');
+ lastMessageTextFromReport = report ? report.lastMessageText : '';
}
const lastActorDetails = personalDetailMap[report.lastActorEmail] || null;
diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js
index df55ae49fae..61484b1d983 100644
--- a/src/libs/ReportActionsUtils.js
+++ b/src/libs/ReportActionsUtils.js
@@ -194,7 +194,7 @@ function getSortedReportActionsForDisplay(reportActions) {
const sortedReportActions = getSortedReportActions(filteredReportActions, true);
return _.filter(sortedReportActions, (reportAction) => {
// Filter out any unsupported reportAction types
- if (!_.has(CONST.REPORT.ACTIONS.TYPE, reportAction.actionName)) {
+ if (!_.has(CONST.REPORT.ACTIONS.TYPE, reportAction.actionName) && !_.contains(_.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG), reportAction.actionName)) {
return false;
}
@@ -213,11 +213,16 @@ function getSortedReportActionsForDisplay(reportActions) {
/**
* In some cases, there can be multiple closed report actions in a chat report.
* This method returns the last closed report action so we can always show the correct archived report reason.
+ * Additionally, archived #admins and #announce do not have the closed report action so we will return null if none is found.
*
* @param {Object} reportActions
- * @returns {Object}
+ * @returns {Object|null}
*/
function getLastClosedReportAction(reportActions) {
+ // If closed report action is not present, return early
+ if (!_.some(reportActions, action => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED)) {
+ return null;
+ }
const filteredReportActions = filterOutDeprecatedReportActions(reportActions);
const sortedReportActions = getSortedReportActions(filteredReportActions);
return lodashFindLast(sortedReportActions, action => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED);
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index bb07c7d25e3..df0a3209e5a 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -986,7 +986,6 @@ function getIOUReportActionMessage(type, total, participants, comment, currency,
const who = displayNames.length < 3
? displayNames.join(' and ')
: `${displayNames.slice(0, -1).join(', ')}, and ${_.last(displayNames)}`;
-
let paymentMethodMessage;
switch (paymentType) {
case CONST.IOU.PAYMENT_TYPE.EXPENSIFY:
diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js
index 3e6fd500c98..d333b194c35 100644
--- a/src/libs/SidebarUtils.js
+++ b/src/libs/SidebarUtils.js
@@ -275,9 +275,9 @@ function getOptionData(reportID) {
}
if (result.isChatRoom || result.isPolicyExpenseChat) {
- result.alternateText = lastMessageText || subtitle;
+ result.alternateText = lastMessageText || Localize.translate(preferredLocale, 'report.noActivityYet');
} else {
- if (hasMultipleParticipants && !lastMessageText) {
+ if (!lastMessageText) {
// Here we get the beginning of chat history message and append the display name for each user, adding pronouns if there are any.
// We also add a fullstop after the final name, the word "and" before the final name and commas between all previous names.
lastMessageText = Localize.translate(preferredLocale, 'reportActionsView.beginningOfChatHistory')
diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js
index bf642a9ab4c..8a3f7ced02f 100644
--- a/src/libs/actions/IOU.js
+++ b/src/libs/actions/IOU.js
@@ -203,7 +203,6 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com
transactionID: optimisticReportAction.originalMessage.IOUTransactionID,
reportActionID: optimisticReportAction.reportActionID,
createdReportActionID: isNewChat ? optimisticCreatedAction.reportActionID : 0,
- shouldKeyReportActionsByID: true,
}, {optimisticData, successData, failureData});
Navigation.navigate(ROUTES.getReportRoute(chatReport.reportID));
}
@@ -501,7 +500,6 @@ function splitBill(participants, currentUserLogin, amount, comment, currency, lo
transactionID: groupData.transactionID,
reportActionID: groupData.reportActionID,
createdReportActionID: groupData.createdReportActionID,
- shouldKeyReportActionsByID: true,
}, onyxData);
Navigation.dismissModal();
@@ -527,7 +525,6 @@ function splitBillAndOpenReport(participants, currentUserLogin, amount, comment,
transactionID: groupData.transactionID,
reportActionID: groupData.reportActionID,
createdReportActionID: groupData.createdReportActionID,
- shouldKeyReportActionsByID: true,
}, onyxData);
Navigation.navigate(ROUTES.getReportRoute(groupData.chatReportID));
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 56e2f246def..bfa44088bd3 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -255,7 +255,6 @@ function addActions(reportID, text = '', file) {
commentReportActionID: file && reportCommentAction ? reportCommentAction.reportActionID : null,
reportComment: reportCommentText,
file,
- shouldKeyReportActionsByID: true,
};
const optimisticData = [
@@ -376,7 +375,6 @@ function openReport(reportID, participantList = [], newReportObject = {}) {
const params = {
reportID,
emailList: participantList ? participantList.join(',') : '',
- shouldKeyReportActionsByID: true,
};
// If we are creating a new report, we need to add the optimistic report data and a report action
@@ -440,7 +438,6 @@ function reconnect(reportID) {
API.write('ReconnectToReport',
{
reportID,
- shouldKeyReportActionsByID: true,
},
{
optimisticData: [{
@@ -517,7 +514,6 @@ function openPaymentDetailsPage(chatReportID, iouReportID) {
API.read('OpenPaymentDetailsPage', {
reportID: chatReportID,
iouReportID,
- shouldKeyReportActionsByID: true,
}, {
optimisticData: [
{
@@ -769,7 +765,6 @@ function deleteReportComment(reportID, reportAction) {
const parameters = {
reportID,
reportActionID: reportAction.reportActionID,
- shouldKeyReportActionsByID: true,
};
API.write('DeleteComment', parameters, {optimisticData, successData, failureData});
}
@@ -929,7 +924,6 @@ function editReportComment(reportID, originalReportAction, textForNewComment) {
reportID,
reportComment: htmlForNewComment,
reportActionID,
- shouldKeyReportActionsByID: true,
};
API.write('UpdateComment', parameters, {optimisticData, successData, failureData});
}
diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js
index cf829d3d898..3b5b83796d4 100755
--- a/src/pages/DetailsPage.js
+++ b/src/pages/DetailsPage.js
@@ -1,6 +1,7 @@
import React from 'react';
import {View, ScrollView} from 'react-native';
import PropTypes from 'prop-types';
+import _ from 'underscore';
import {withOnyx} from 'react-native-onyx';
import Str from 'expensify-common/lib/str';
import lodashGet from 'lodash/get';
@@ -25,6 +26,7 @@ import PressableWithoutFocus from '../components/PressableWithoutFocus';
import * as Report from '../libs/actions/Report';
import OfflineWithFeedback from '../components/OfflineWithFeedback';
import AutoUpdateTime from '../components/AutoUpdateTime';
+import FullPageNotFoundView from '../components/BlockingViews/FullPageNotFoundView';
const matchType = PropTypes.shape({
params: PropTypes.shape({
@@ -80,20 +82,12 @@ const getPhoneNumber = (details) => {
};
class DetailsPage extends React.PureComponent {
- componentDidMount() {
- if (lodashGet(this.props.route.params, 'login')) {
- return;
- }
-
- // Leave the page when the login information is not available
- Navigation.dismissModal();
- }
-
render() {
- let details = lodashGet(this.props.personalDetails, lodashGet(this.props.route.params, 'login'));
+ const login = lodashGet(this.props.route.params, 'login', '');
+ const reportID = lodashGet(this.props.route.params, 'reportID', '');
+ let details = lodashGet(this.props.personalDetails, login);
if (!details) {
- const login = lodashGet(this.props.route.params, 'login');
details = {
login,
displayName: ReportUtils.getDisplayNameForParticipant(login),
@@ -105,7 +99,7 @@ class DetailsPage extends React.PureComponent {
// If we have a reportID param this means that we
// arrived here via the ParticipantsPage and should be allowed to navigate back to it
- const shouldShowBackButton = Boolean(this.props.route.params.reportID);
+ const shouldShowBackButton = Boolean(reportID);
const shouldShowLocalTime = !ReportUtils.hasAutomatedExpensifyEmails([details.login]) && details.timezone;
let pronouns = details.pronouns;
@@ -116,91 +110,93 @@ class DetailsPage extends React.PureComponent {
return (
- Navigation.goBack()}
- onCloseButtonPress={() => Navigation.dismissModal()}
- />
-
- {details ? (
-
-
-
- {({show}) => (
-
-
+ Navigation.goBack()}
+ onCloseButtonPress={() => Navigation.dismissModal()}
+ />
+
+ {details ? (
+
+
+
+ {({show}) => (
+
-
-
-
+
+
+
+
+ )}
+
+ {details.displayName && (
+
+ {isSMSLogin ? this.props.toLocalPhone(details.displayName) : details.displayName}
+
)}
-
- {details.displayName && (
-
- {isSMSLogin ? this.props.toLocalPhone(details.displayName) : details.displayName}
-
+ {details.login ? (
+
+
+ {this.props.translate(isSMSLogin
+ ? 'common.phoneNumber'
+ : 'common.email')}
+
+
+
+
+ {isSMSLogin
+ ? this.props.toLocalPhone(getPhoneNumber(details))
+ : details.login}
+
+
+
+
+ ) : null}
+ {pronouns ? (
+
+
+ {this.props.translate('profilePage.preferredPronouns')}
+
+
+ {pronouns}
+
+
+ ) : null}
+ {shouldShowLocalTime && }
+
+ {details.login !== this.props.session.email && (
+
- {details.login !== this.props.session.email && (
-
+
+ ) : null}
+
+
);
}
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index 0c5066e652c..6594ec21590 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -93,7 +93,7 @@ const HeaderView = (props) => {
diff --git a/src/pages/home/report/FloatingMessageCounter/index.js b/src/pages/home/report/FloatingMessageCounter/index.js
index 2297f8fc536..5754993fbda 100644
--- a/src/pages/home/report/FloatingMessageCounter/index.js
+++ b/src/pages/home/report/FloatingMessageCounter/index.js
@@ -63,7 +63,7 @@ class FloatingMessageCounter extends PureComponent {
render() {
return (
diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js
index d06e75de566..404b660020d 100644
--- a/src/pages/home/report/ReportActionItem.js
+++ b/src/pages/home/report/ReportActionItem.js
@@ -38,6 +38,7 @@ import focusTextInputAfterAnimation from '../../../libs/focusTextInputAfterAnima
import ChronosOOOListActions from '../../../components/ReportActionItem/ChronosOOOListActions';
import ReportActionItemReactions from '../../../components/Reactions/ReportActionItemReactions';
import * as Report from '../../../libs/actions/Report';
+import withLocalize from '../../../components/withLocalize';
const propTypes = {
/** Report for this action */
@@ -126,7 +127,7 @@ class ReportActionItem extends Component {
// Newline characters need to be removed here because getCurrentSelection() returns html mixed with newlines, and when
//
tags are converted later to markdown, it creates duplicate newline characters. This means that when the content
// is pasted, there are extra newlines in the content that we want to avoid.
- const selection = SelectionScraper.getCurrentSelection().replace(/\n/g, '');
+ const selection = SelectionScraper.getCurrentSelection().replace(/
\n/g, '
');
ReportActionContextMenu.showContextMenu(
ContextMenuActions.CONTEXT_MENU_TYPES.REPORT_ACTION,
event,
@@ -182,7 +183,10 @@ class ReportActionItem extends Component {
? (
) : (
{hovered => (
-
+
{this.props.shouldDisplayNewMarker && (
)}
@@ -305,6 +309,7 @@ ReportActionItem.defaultProps = defaultProps;
export default compose(
withWindowDimensions,
+ withLocalize,
withNetwork(),
withBlockedFromConcierge({propName: 'blockedFromConcierge'}),
withReportActionsDrafts({
diff --git a/src/pages/home/report/ReportActionItemCreated.js b/src/pages/home/report/ReportActionItemCreated.js
index 89cda085bdf..f942b9e0513 100644
--- a/src/pages/home/report/ReportActionItemCreated.js
+++ b/src/pages/home/report/ReportActionItemCreated.js
@@ -16,6 +16,7 @@ import EmptyStateBackgroundImage from '../../../../assets/images/empty-state_bac
import * as StyleUtils from '../../../styles/StyleUtils';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import compose from '../../../libs/compose';
+import withLocalize from '../../../components/withLocalize';
const propTypes = {
/** The id of the report */
@@ -57,7 +58,7 @@ const ReportActionItemCreated = (props) => {
style={StyleUtils.getReportWelcomeBackgroundImageStyle(props.isSmallScreenWidth)}
/>
`${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js
index 04a57a1178a..743192fa610 100644
--- a/src/pages/home/report/ReportActionItemFragment.js
+++ b/src/pages/home/report/ReportActionItemFragment.js
@@ -142,7 +142,7 @@ const ReportActionItemFragment = (props) => {
numberOfLines={props.isSingleLine ? 1 : undefined}
style={[styles.chatItemMessageHeaderSender, (props.isSingleLine ? styles.pre : styles.preWrap)]}
>
- {Str.htmlDecode(props.fragment.text)}
+ {props.fragment.text}
);
diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js
index 38d19864ea5..2e02a794ed0 100644
--- a/src/pages/home/report/ReportActionsList.js
+++ b/src/pages/home/report/ReportActionsList.js
@@ -20,6 +20,7 @@ import CONST from '../../../CONST';
import * as StyleUtils from '../../../styles/StyleUtils';
import reportPropTypes from '../../reportPropTypes';
import networkPropTypes from '../../../components/networkPropTypes';
+import withLocalize from '../../../components/withLocalize';
const propTypes = {
/** Position of the "New" line marker */
@@ -150,7 +151,7 @@ class ReportActionsList extends React.Component {
return (
@@ -122,6 +123,8 @@ class PronounsPage extends Component {
-
+
this.input = el}
label={this.props.translate('loginForm.phoneOrEmail')}
diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js
index 17893cb94c8..2ffadfc7c26 100644
--- a/src/styles/StyleUtils.js
+++ b/src/styles/StyleUtils.js
@@ -458,7 +458,7 @@ function getFontFamilyMonospace({fontStyle, fontWeight}) {
function getEmojiPickerStyle(isSmallScreenWidth) {
if (isSmallScreenWidth) {
return {
- width: '100%',
+ width: CONST.SMALL_EMOJI_PICKER_SIZE.WIDTH,
};
}
return {
@@ -811,6 +811,18 @@ function getReportWelcomeContainerStyle(isSmallScreenWidth) {
};
}
+/**
+ * Gets the correct height for emoji picker list based on screen dimensions
+ *
+ * @param {Boolean} hasAdditionalSpace
+ * @returns {Object}
+ */
+function getEmojiPickerListHeight(hasAdditionalSpace) {
+ return {
+ height: hasAdditionalSpace ? CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT + CONST.CATEGORY_SHORTCUT_BAR_HEIGHT : CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT,
+ };
+}
+
/**
* Gets styles for Emoji Suggestion row
*
@@ -967,6 +979,7 @@ export {
getReportWelcomeBackgroundImageStyle,
getReportWelcomeTopMarginStyle,
getReportWelcomeContainerStyle,
+ getEmojiPickerListHeight,
getEmojiSuggestionItemStyle,
getEmojiSuggestionContainerStyle,
getColoredBackgroundStyle,
diff --git a/src/styles/styles.js b/src/styles/styles.js
index d59ca9ae9a2..fafa5d1267d 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -1523,12 +1523,25 @@ const styles = {
emojiPickerContainer: {
backgroundColor: themeColors.componentBG,
},
-
emojiPickerList: {
- height: 288,
+ height: CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT,
+ width: '100%',
+ ...spacing.ph4,
+ },
+ emojiPickerListWithPadding: {
+ height: CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT + CONST.CATEGORY_SHORTCUT_BAR_HEIGHT,
width: '100%',
...spacing.ph4,
},
+ emojiPickerSearchListContainer: {
+ position: 'absolute',
+ top: 60,
+ right: 0,
+ bottom: 4,
+ left: 0,
+ backgroundColor: themeColors.appBG,
+ },
+
emojiPickerListLandscape: {
height: 240,
},
diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js
index 5f65d8b20b5..ad2d622a431 100644
--- a/tests/ui/UnreadIndicatorsTest.js
+++ b/tests/ui/UnreadIndicatorsTest.js
@@ -24,6 +24,7 @@ import * as User from '../../src/libs/actions/User';
import * as Pusher from '../../src/libs/Pusher/pusher';
import PusherConnectionManager from '../../src/libs/PusherConnectionManager';
import CONFIG from '../../src/CONFIG';
+import * as Localize from '../../src/libs/Localize';
// 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);
@@ -51,7 +52,8 @@ beforeAll(() => {
});
function scrollUpToRevealNewMessagesBadge() {
- fireEvent.scroll(screen.queryByLabelText('List of chat messages'), {
+ const hintText = Localize.translateLocal('sidebarScreen.listOfChatMessages');
+ fireEvent.scroll(screen.queryByLabelText(hintText), {
nativeEvent: {
contentOffset: {
y: 250,
@@ -74,7 +76,8 @@ function scrollUpToRevealNewMessagesBadge() {
* @return {Boolean}
*/
function isNewMessagesBadgeVisible() {
- const badge = screen.queryByAccessibilityHint('Scroll to newest messages');
+ const hintText = Localize.translateLocal('accessibilityHints.scrollToNewestMessages');
+ const badge = screen.queryByAccessibilityHint(hintText);
return Math.round(badge.props.style.transform[0].translateY) === 10;
}
@@ -82,7 +85,8 @@ function isNewMessagesBadgeVisible() {
* @return {Promise}
*/
function navigateToSidebar() {
- const reportHeaderBackButton = screen.queryByAccessibilityHint('Navigate back to chats list');
+ const hintText = Localize.translateLocal('accessibilityHints.navigateToChatsList');
+ const reportHeaderBackButton = screen.queryByAccessibilityHint(hintText);
fireEvent(reportHeaderBackButton, 'press');
return waitForPromisesToResolve();
}
@@ -92,7 +96,8 @@ function navigateToSidebar() {
* @return {Promise}
*/
function navigateToSidebarOption(index) {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
fireEvent(optionRows[index], 'press');
return waitForPromisesToResolve();
}
@@ -101,7 +106,8 @@ function navigateToSidebarOption(index) {
* @return {Boolean}
*/
function isDrawerOpen() {
- const sidebarLinks = screen.queryAllByLabelText('List of chats');
+ const hintText = Localize.translateLocal('sidebarScreen.listOfChats');
+ const sidebarLinks = screen.queryAllByLabelText(hintText);
return !lodashGet(sidebarLinks, [0, 'props', 'accessibilityElementsHidden']);
}
@@ -126,7 +132,8 @@ function signInAndGetAppWithUnreadChat() {
render();
return waitForPromisesToResolveWithAct()
.then(() => {
- const loginForm = screen.queryAllByLabelText('Login form');
+ const hintText = Localize.translateLocal('loginForm.loginForm');
+ const loginForm = screen.queryAllByLabelText(hintText);
expect(loginForm).toHaveLength(1);
return TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A');
@@ -201,16 +208,19 @@ describe('Unread Indicators', () => {
expect(LocalNotification.showCommentNotification.mock.calls).toHaveLength(0);
// Verify the sidebar links are rendered
- const sidebarLinks = screen.queryAllByLabelText('List of chats');
+ const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats');
+ const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText);
expect(sidebarLinks).toHaveLength(1);
expect(isDrawerOpen()).toBe(true);
// Verify there is only one option in the sidebar
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText);
expect(optionRows).toHaveLength(1);
// And that the text is bold
- const displayNameText = screen.queryByLabelText('Chat user display names');
+ const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNameText = screen.queryByLabelText(displayNameHintText);
expect(lodashGet(displayNameText, ['props', 'style', 0, 'fontWeight'])).toBe(fontWeightBold);
return navigateToSidebarOption(0);
@@ -220,14 +230,17 @@ describe('Unread Indicators', () => {
expect(isDrawerOpen()).toBe(false);
// That the report actions are visible along with the created action
- const createdAction = screen.queryByLabelText('Chat welcome message');
+ const welcomeMessageHintText = Localize.translateLocal('accessibilityHints.chatWelcomeMessage');
+ const createdAction = screen.queryByLabelText(welcomeMessageHintText);
expect(createdAction).toBeTruthy();
- const reportComments = screen.queryAllByLabelText('Chat message');
+ const reportCommentsHintText = Localize.translateLocal('accessibilityHints.chatMessage');
+ const reportComments = screen.queryAllByLabelText(reportCommentsHintText);
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 unreadIndicator = screen.queryAllByLabelText('New message line indicator');
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
+ const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(1);
const reportActionID = lodashGet(unreadIndicator, [0, 'props', 'data-action-id']);
expect(reportActionID).toBe('4');
@@ -252,7 +265,8 @@ describe('Unread Indicators', () => {
expect(isDrawerOpen()).toBe(true);
// Verify that the option row in the LHN is no longer bold (since OpenReport marked it as read)
- const updatedDisplayNameText = screen.queryByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const updatedDisplayNameText = screen.queryByLabelText(hintText);
expect(lodashGet(updatedDisplayNameText, ['props', 'style', 0, 'fontWeight'])).toBe(undefined);
// Tap on the chat again
@@ -260,7 +274,8 @@ describe('Unread Indicators', () => {
})
.then(() => {
// Verify the unread indicator is not present
- const unreadIndicator = screen.queryAllByLabelText('New message line indicator');
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
+ const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(0);
expect(isDrawerOpen()).toBe(false);
@@ -334,11 +349,13 @@ describe('Unread Indicators', () => {
})
.then(() => {
// // Verify the new report option appears in the LHN
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText);
expect(optionRows).toHaveLength(2);
// Verify the text for both chats are bold indicating that nothing has not yet been read
- const displayNameTexts = screen.queryAllByLabelText('Chat user display names');
+ const displayNameHintTexts = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNameTexts = screen.queryAllByLabelText(displayNameHintTexts);
expect(displayNameTexts).toHaveLength(2);
const firstReportOption = displayNameTexts[0];
expect(lodashGet(firstReportOption, ['props', 'style', 0, 'fontWeight'])).toBe(fontWeightBold);
@@ -353,7 +370,8 @@ describe('Unread Indicators', () => {
})
.then(() => {
// Verify that report we navigated to appears in a "read" state while the original unread report still shows as unread
- const displayNameTexts = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNameTexts = screen.queryAllByLabelText(hintText);
expect(displayNameTexts).toHaveLength(2);
expect(lodashGet(displayNameTexts[0], ['props', 'style', 0, 'fontWeight'])).toBe(undefined);
expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('C User');
@@ -373,7 +391,8 @@ describe('Unread Indicators', () => {
})
.then(() => {
// Verify the indicator appears above the last action
- const unreadIndicator = screen.queryAllByLabelText('New message line indicator');
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
+ const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(1);
const reportActionID = lodashGet(unreadIndicator, [0, 'props', 'data-action-id']);
expect(reportActionID).toBe('3');
@@ -387,7 +406,8 @@ describe('Unread Indicators', () => {
.then(navigateToSidebar)
.then(() => {
// Verify the report is marked as unread in the sidebar
- const displayNameTexts = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNameTexts = screen.queryAllByLabelText(hintText);
expect(displayNameTexts).toHaveLength(1);
expect(lodashGet(displayNameTexts[0], ['props', 'style', 0, 'fontWeight'])).toBe(fontWeightBold);
expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('B User');
@@ -398,7 +418,8 @@ describe('Unread Indicators', () => {
.then(() => navigateToSidebar())
.then(() => {
// Verify the report is now marked as read
- const displayNameTexts = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNameTexts = screen.queryAllByLabelText(hintText);
expect(displayNameTexts).toHaveLength(1);
expect(lodashGet(displayNameTexts[0], ['props', 'style', 0, 'fontWeight'])).toBe(undefined);
expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('B User');
@@ -407,7 +428,8 @@ describe('Unread Indicators', () => {
return navigateToSidebarOption(0);
})
.then(() => {
- const unreadIndicator = screen.queryAllByLabelText('New message line indicator');
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
+ const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(0);
// Scroll up and verify the "New messages" badge is hidden
@@ -420,7 +442,8 @@ describe('Unread Indicators', () => {
// Verify we are on the LHN and that the chat shows as unread in the LHN
expect(isDrawerOpen()).toBe(true);
- const displayNameTexts = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNameTexts = screen.queryAllByLabelText(hintText);
expect(displayNameTexts).toHaveLength(1);
expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('B User');
expect(lodashGet(displayNameTexts[0], ['props', 'style', 0, 'fontWeight'])).toBe(fontWeightBold);
@@ -429,7 +452,8 @@ describe('Unread Indicators', () => {
return navigateToSidebarOption(0);
})
.then(() => {
- const unreadIndicator = screen.queryAllByLabelText('New message line indicator');
+ 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 removed
@@ -437,7 +461,8 @@ describe('Unread Indicators', () => {
return waitForPromisesToResolve();
})
.then(() => {
- const unreadIndicator = screen.queryAllByLabelText('New message line indicator');
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
+ const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(0);
}));
@@ -446,7 +471,8 @@ describe('Unread Indicators', () => {
// Verify we are on the LHN and that the chat shows as unread in the LHN
expect(isDrawerOpen()).toBe(true);
- const displayNameTexts = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNameTexts = screen.queryAllByLabelText(hintText);
expect(displayNameTexts).toHaveLength(1);
expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('B User');
expect(lodashGet(displayNameTexts[0], ['props', 'style', 0, 'fontWeight'])).toBe(fontWeightBold);
@@ -455,7 +481,8 @@ describe('Unread Indicators', () => {
return navigateToSidebarOption(0);
})
.then(() => {
- const unreadIndicator = screen.queryAllByLabelText('New message line indicator');
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
+ const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(1);
// Then back to the LHN - then back to the chat again and verify the new line indicator has cleared
@@ -463,7 +490,8 @@ describe('Unread Indicators', () => {
})
.then(() => navigateToSidebarOption(0))
.then(() => {
- const unreadIndicator = screen.queryAllByLabelText('New message line indicator');
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
+ const unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(0);
// Mark a previous comment as unread and verify the unread action indicator returns
@@ -471,7 +499,8 @@ describe('Unread Indicators', () => {
return waitForPromisesToResolve();
})
.then(() => {
- let unreadIndicator = screen.queryAllByLabelText('New message line indicator');
+ const newMessageLineIndicatorHintText = Localize.translateLocal('accessibilityHints.newMessageLineIndicator');
+ let unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(1);
// Trigger the app going inactive and active again
@@ -479,7 +508,7 @@ describe('Unread Indicators', () => {
AppState.emitCurrentTestState('active');
// Verify the new line is still present
- unreadIndicator = screen.queryAllByLabelText('New message line indicator');
+ unreadIndicator = screen.queryAllByLabelText(newMessageLineIndicatorHintText);
expect(unreadIndicator).toHaveLength(1);
}));
@@ -512,7 +541,8 @@ describe('Unread Indicators', () => {
})
.then(() => {
// Verify the chat preview text matches the last comment from the current user
- const alternateText = screen.queryAllByLabelText('Last chat message preview');
+ const hintText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview');
+ const alternateText = screen.queryAllByLabelText(hintText);
expect(alternateText).toHaveLength(1);
expect(alternateText[0].props.children).toBe('Current User Comment 1');
@@ -520,7 +550,8 @@ describe('Unread Indicators', () => {
return waitForPromisesToResolve();
})
.then(() => {
- const alternateText = screen.queryAllByLabelText('Last chat message preview');
+ const hintText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview');
+ const alternateText = screen.queryAllByLabelText(hintText);
expect(alternateText).toHaveLength(1);
expect(alternateText[0].props.children).toBe('Comment 9');
});
diff --git a/tests/unit/ReportActionsUtilsTest.js b/tests/unit/ReportActionsUtilsTest.js
index 20bf389969b..6b8fdbb04fa 100644
--- a/tests/unit/ReportActionsUtilsTest.js
+++ b/tests/unit/ReportActionsUtilsTest.js
@@ -143,6 +143,12 @@ describe('ReportActionsUtils', () => {
},
{
created: '2022-11-09 22:27:01.825',
+ reportActionID: '8049485084562457',
+ actionName: CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.UPDATE_FIELD,
+ message: [{html: 'updated the Approval Mode from "Submit and Approve" to "Submit and Close"'}],
+ },
+ {
+ created: '2022-11-08 22:27:01.825',
reportActionID: '1661970171066218',
actionName: 'REIMBURSED',
message: [{html: 'Hello world'}],
diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js
index 47ead27d878..a393199f2d4 100644
--- a/tests/unit/SidebarFilterTest.js
+++ b/tests/unit/SidebarFilterTest.js
@@ -5,6 +5,7 @@ import * as LHNTestUtils from '../utils/LHNTestUtils';
import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
import CONST from '../../src/CONST';
import DateUtils from '../../src/libs/DateUtils';
+import * as Localize from '../../src/libs/Localize';
// Be sure to include the mocked permissions library or else the beta tests won't work
jest.mock('../../src/libs/Permissions');
@@ -53,7 +54,8 @@ describe('Sidebar', () => {
// Then no reports are rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(0);
});
});
@@ -79,7 +81,8 @@ describe('Sidebar', () => {
// Then no reports are rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(0);
})
@@ -90,7 +93,8 @@ describe('Sidebar', () => {
// Then there is one report rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(1);
});
});
@@ -116,7 +120,8 @@ describe('Sidebar', () => {
// Then no reports are rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(0);
})
@@ -127,7 +132,8 @@ describe('Sidebar', () => {
// Then there is one report rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(1);
});
});
@@ -163,7 +169,8 @@ describe('Sidebar', () => {
// Then no reports are rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(0);
})
@@ -174,7 +181,8 @@ describe('Sidebar', () => {
// Then all three reports are showing in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(3);
});
});
@@ -206,7 +214,8 @@ describe('Sidebar', () => {
// Then the report is rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(1);
})
@@ -215,7 +224,8 @@ describe('Sidebar', () => {
// Then the report is not rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(0);
});
});
@@ -295,13 +305,16 @@ describe('Sidebar', () => {
.then(() => {
if (booleansWhichRemovesActiveReport.indexOf(JSON.stringify(boolArr)) > -1) {
// Only one report visible
- expect(screen.queryAllByAccessibilityHint('Navigates to a chat')).toHaveLength(1);
- expect(screen.queryAllByLabelText('Chat user display names')).toHaveLength(1);
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const displayNamesHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(displayNamesHintText);
+ const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(1);
+ expect(displayNames).toHaveLength(1);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Three, Four');
} else {
// Both reports visible
- expect(screen.queryAllByAccessibilityHint('Navigates to a chat')).toHaveLength(2);
+ const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(2);
}
});
});
@@ -331,7 +344,8 @@ describe('Sidebar', () => {
// Then the reports 1 and 2 are shown and 3 is not
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(2);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('One, Two');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Three, Four');
@@ -345,7 +359,8 @@ describe('Sidebar', () => {
// Then all three chats are showing
.then(() => {
- expect(screen.queryAllByAccessibilityHint('Navigates to a chat')).toHaveLength(3);
+ const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(3);
})
// When report 1 becomes read (it's the active report)
@@ -353,7 +368,8 @@ describe('Sidebar', () => {
// Then all three chats are still showing
.then(() => {
- expect(screen.queryAllByAccessibilityHint('Navigates to a chat')).toHaveLength(3);
+ const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(3);
})
// When report 2 becomes the active report
@@ -364,7 +380,8 @@ describe('Sidebar', () => {
// Then report 1 should now disappear
.then(() => {
- expect(screen.queryAllByAccessibilityHint('Navigates to a chat')).toHaveLength(2);
+ const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(2);
expect(screen.queryAllByText(/One, Two/)).toHaveLength(0);
});
});
@@ -393,7 +410,8 @@ describe('Sidebar', () => {
// Then both reports are visible
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(2);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Three, Four');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
@@ -441,7 +459,8 @@ describe('Sidebar', () => {
// Then neither reports are visible
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(0);
})
@@ -461,7 +480,8 @@ describe('Sidebar', () => {
// Then they are all visible
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
});
});
@@ -497,7 +517,8 @@ describe('Sidebar', () => {
// Then neither reports are visible
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(0);
})
@@ -514,7 +535,8 @@ describe('Sidebar', () => {
// Then both rooms are visible
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(2);
});
});
@@ -595,13 +617,16 @@ describe('Sidebar', () => {
.then(() => {
if (booleansWhichRemovesActiveReport.indexOf(JSON.stringify(boolArr)) > -1) {
// Only one report visible
- expect(screen.queryAllByAccessibilityHint('Navigates to a chat')).toHaveLength(1);
- expect(screen.queryAllByLabelText('Chat user display names')).toHaveLength(1);
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const displayNamesHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(displayNamesHintText);
+ const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(1);
+ expect(displayNames).toHaveLength(1);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Three, Four');
} else {
// Both reports visible
- expect(screen.queryAllByAccessibilityHint('Navigates to a chat')).toHaveLength(2);
+ const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(2);
}
});
});
@@ -640,7 +665,8 @@ describe('Sidebar', () => {
// Then the report is rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(1);
})
@@ -651,7 +677,8 @@ describe('Sidebar', () => {
// Then the report is rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(1);
});
});
@@ -687,7 +714,8 @@ describe('Sidebar', () => {
// Then the report is not rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(0);
})
@@ -699,7 +727,8 @@ describe('Sidebar', () => {
// Then the report is rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(1);
});
});
@@ -734,7 +763,8 @@ describe('Sidebar', () => {
// Then the report is not rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(0);
})
@@ -743,7 +773,8 @@ describe('Sidebar', () => {
// Then the report is rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(1);
});
});
@@ -777,7 +808,8 @@ describe('Sidebar', () => {
// Then the report is not rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(0);
})
@@ -789,7 +821,8 @@ describe('Sidebar', () => {
// Then the report is rendered in the LHN
.then(() => {
- const optionRows = screen.queryAllByAccessibilityHint('Navigates to a chat');
+ const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ const optionRows = screen.queryAllByAccessibilityHint(hintText);
expect(optionRows).toHaveLength(1);
});
});
diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js
index 6072bccca3d..0706ccf2ab9 100644
--- a/tests/unit/SidebarOrderTest.js
+++ b/tests/unit/SidebarOrderTest.js
@@ -5,6 +5,7 @@ import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
import * as LHNTestUtils from '../utils/LHNTestUtils';
import CONST from '../../src/CONST';
import DateUtils from '../../src/libs/DateUtils';
+import * as Localize from '../../src/libs/Localize';
// Be sure to include the mocked Permissions and Expensicons libraries or else the beta tests won't work
jest.mock('../../src/libs/Permissions');
@@ -62,7 +63,8 @@ describe('Sidebar', () => {
// Then the component should be rendered with an empty list since it will get past the early return
.then(() => {
expect(screen.toJSON()).not.toBe(null);
- expect(screen.queryAllByAccessibilityHint('Navigates to a chat')).toHaveLength(0);
+ const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat');
+ expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(0);
});
});
@@ -113,7 +115,8 @@ describe('Sidebar', () => {
// Then the component should be rendered with the mostly recently updated report first
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Three, Four');
@@ -147,10 +150,11 @@ describe('Sidebar', () => {
// Then there should be a pencil icon and report one should be the first one because putting a draft on the active report should change its location
// in the ordered list
.then(() => {
- const pencilIcon = screen.getAllByAccessibilityHint('Pencil Icon');
+ const pencilIcon = screen.queryAllByTestId('Pencil Icon');
expect(pencilIcon).toHaveLength(1);
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('One, Two'); // this has `hasDraft` flag enabled so it will be on top
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Five, Six');
@@ -185,7 +189,8 @@ describe('Sidebar', () => {
// Then the order of the reports should be 1 > 3 > 2
// ^--- (1 goes to the front and pushes other two down)
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('One, Two');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Five, Six');
@@ -228,7 +233,8 @@ describe('Sidebar', () => {
// Then the order of the reports should be 2 > 3 > 1
// ^--- (2 goes to the front and pushes 3 down)
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Three, Four');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Five, Six');
@@ -257,7 +263,7 @@ describe('Sidebar', () => {
// Then there should be a pencil icon showing
.then(() => {
- expect(screen.getAllByAccessibilityHint('Pencil Icon')).toHaveLength(1);
+ expect(screen.queryAllByTestId('Pencil Icon')).toHaveLength(1);
})
// When the draft is removed
@@ -265,7 +271,7 @@ describe('Sidebar', () => {
// Then the pencil icon goes away
.then(() => {
- expect(screen.queryAllByAccessibilityHint('Pencil Icon')).toHaveLength(0);
+ expect(screen.queryAllByTestId('Pencil Icon')).toHaveLength(0);
});
});
@@ -290,7 +296,7 @@ describe('Sidebar', () => {
// Then there should be a pencil icon showing
.then(() => {
- expect(screen.getAllByAccessibilityHint('Pin Icon')).toHaveLength(1);
+ expect(screen.queryAllByTestId('Pin Icon')).toHaveLength(1);
})
// When the draft is removed
@@ -298,7 +304,7 @@ describe('Sidebar', () => {
// Then the pencil icon goes away
.then(() => {
- expect(screen.queryAllByAccessibilityHint('Pin Icon')).toHaveLength(0);
+ expect(screen.queryAllByTestId('Pin Icon')).toHaveLength(0);
});
});
@@ -352,10 +358,11 @@ describe('Sidebar', () => {
// there is a pencil icon
// there is a pinned icon
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
- expect(screen.getAllByAccessibilityHint('Pin Icon')).toHaveLength(1);
- expect(screen.getAllByAccessibilityHint('Pencil Icon')).toHaveLength(1);
+ expect(screen.queryAllByTestId('Pin Icon')).toHaveLength(1);
+ expect(screen.queryAllByTestId('Pencil Icon')).toHaveLength(1);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('One, Two');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Five, Six');
expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
@@ -395,7 +402,8 @@ describe('Sidebar', () => {
// Then the reports are in alphabetical order
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
@@ -407,7 +415,8 @@ describe('Sidebar', () => {
// Then they are still in alphabetical order
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
@@ -449,7 +458,8 @@ describe('Sidebar', () => {
// Then the reports are in alphabetical order
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
@@ -461,7 +471,8 @@ describe('Sidebar', () => {
// Then they are still in alphabetical order
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
@@ -502,7 +513,8 @@ describe('Sidebar', () => {
// Then the first report is in last position
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Three, Four');
@@ -534,7 +546,8 @@ describe('Sidebar', () => {
// Then the reports are in alphabetical order
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
@@ -546,7 +559,8 @@ describe('Sidebar', () => {
// Then they are still in alphabetical order
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(4);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
@@ -587,7 +601,8 @@ describe('Sidebar', () => {
// Then the first report is in last position
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Three, Four');
@@ -666,7 +681,8 @@ describe('Sidebar', () => {
// Then the reports are ordered alphabetically since their amounts are the same
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
@@ -704,7 +720,8 @@ describe('Sidebar', () => {
// Then the reports are ordered alphabetically since their lastVisibleActionCreated are the same
.then(() => {
- const displayNames = screen.queryAllByLabelText('Chat user display names');
+ const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
+ const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(3);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
diff --git a/web/proxy.js b/web/proxy.js
index 768963d0810..3bce0851460 100644
--- a/web/proxy.js
+++ b/web/proxy.js
@@ -1,21 +1,18 @@
const http = require('http');
const https = require('https');
+const proxyConfig = require('../config/proxyConfig');
require('dotenv').config();
if (process.env.USE_WEB_PROXY === 'false') {
process.stdout.write('Skipping proxy as USE_WEB_PROXY was set to false.\n');
process.exit();
}
-
-let host = 'www.expensify.com';
-
-// If we are testing against the staging API then we must use the correct host here or nothing with work.
-if (/staging/.test(process.env.EXPENSIFY_URL)) {
- host = 'staging.expensify.com';
-}
+const host = 'www.expensify.com';
+const stagingHost = 'staging.expensify.com';
+const stagingSecureHost = 'staging-secure.expensify.com';
// eslint-disable-next-line no-console
-console.log(`Creating proxy with host: ${host}`);
+console.log(`Creating proxy with host: ${host} for production API and ${stagingHost} for staging API`);
/**
* Local proxy server that hits the production endpoint
@@ -24,13 +21,36 @@ console.log(`Creating proxy with host: ${host}`);
* environment that has no local API.
*/
const server = http.createServer((request, response) => {
+ let hostname = host;
+ let requestPath = request.url;
+
+ /**
+ * When a request is matching a proxy config path we might direct it to a different host (e.g. staging)
+ * For requests matching proxy config patterns we replace the mapping url (prefix) with the actual path.
+ * This is done because the staging api root is only intended for the proxy,
+ * the actual server request must use the /api path.
+ * For example,
+ * /api?command=OpenReport => request sent to production server
+ * /staging/api?command=OpenReport => request sent to staging server
+ * /staging-secure/api?command=OpenReport => request sent to secure staging server
+ * /chat-attachments/46545... => request sent to production server
+ * /staging/chat-attachments/46545... => request sent to staging server
+ */
+ if (request.url.startsWith(proxyConfig.STAGING_SECURE)) {
+ hostname = stagingSecureHost;
+ requestPath = request.url.replace(proxyConfig.STAGING_SECURE, '/');
+ } else if (request.url.startsWith(proxyConfig.STAGING)) {
+ hostname = stagingHost;
+ requestPath = request.url.replace(proxyConfig.STAGING, '/');
+ }
+
const proxyRequest = https.request({
- hostname: host,
+ hostname,
method: 'POST',
- path: request.url,
+ path: requestPath,
headers: {
...request.headers,
- host,
+ host: hostname,
'user-agent': request.headers['user-agent'].concat(' Development-NewDot/1.0'),
},
port: 443,