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 && ( + Report.navigateToAndOpenReport([details.login])} + wrapperStyle={styles.breakAll} + shouldShowRightIcon + /> )} - {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 && ( - Report.navigateToAndOpenReport([details.login])} - wrapperStyle={styles.breakAll} - shouldShowRightIcon - /> - )} - - ) : null} - + + ) : 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,