diff --git a/app/components/edit_channel_info/__snapshots__/edit_channel_info.test.js.snap b/app/components/edit_channel_info/__snapshots__/edit_channel_info.test.js.snap index 0f00e9804..729536436 100644 --- a/app/components/edit_channel_info/__snapshots__/edit_channel_info.test.js.snap +++ b/app/components/edit_channel_info/__snapshots__/edit_channel_info.test.js.snap @@ -30,6 +30,7 @@ exports[`EditChannelInfo should match snapshot 1`] = ` "flex": 1, } } + testID="edit_channel_info" > @@ -161,7 +162,7 @@ exports[`EditChannelInfo should match snapshot 1`] = ` }, ] } - testID="edit_channel.purpose.input" + testID="edit_channel_info.purpose.input" textAlignVertical="top" underlineColorAndroid="transparent" value="purpose" @@ -251,7 +252,7 @@ exports[`EditChannelInfo should match snapshot 1`] = ` }, ] } - testID="edit_channel.header.input" + testID="edit_channel_info.header.input" textAlignVertical="top" underlineColorAndroid="transparent" value="header" diff --git a/app/components/edit_channel_info/edit_channel_info.test.js b/app/components/edit_channel_info/edit_channel_info.test.js index c20dd81af..696d99575 100644 --- a/app/components/edit_channel_info/edit_channel_info.test.js +++ b/app/components/edit_channel_info/edit_channel_info.test.js @@ -11,6 +11,7 @@ import EditChannelInfo from './index'; describe('EditChannelInfo', () => { const baseProps = { + testID: 'edit_channel_info', theme: Preferences.THEMES.default, deviceWidth: 400, deviceHeight: 600, diff --git a/app/components/edit_channel_info/index.js b/app/components/edit_channel_info/index.js index 391be69dd..b0ff25f05 100644 --- a/app/components/edit_channel_info/index.js +++ b/app/components/edit_channel_info/index.js @@ -228,7 +228,10 @@ export default class EditChannelInfo extends PureComponent { style={style.errorContainer} > - + ); @@ -263,7 +266,7 @@ export default class EditChannelInfo extends PureComponent { - + - + { const fuse = new Fuse(emojis, options); const baseProps = { + testID: 'emoji_picker', actions: { getCustomEmojis: jest.fn(), incrementEmojiPickerPage: jest.fn(), diff --git a/app/components/emoji_picker/emoji_picker_base.js b/app/components/emoji_picker/emoji_picker_base.js index c265ddbbe..ca8cceae0 100644 --- a/app/components/emoji_picker/emoji_picker_base.js +++ b/app/components/emoji_picker/emoji_picker_base.js @@ -41,6 +41,7 @@ export function filterEmojiSearchInput(searchText) { export default class EmojiPicker extends PureComponent { static propTypes = { + testID: PropTypes.string, customEmojisEnabled: PropTypes.bool.isRequired, customEmojiPage: PropTypes.number.isRequired, deviceWidth: PropTypes.number.isRequired, diff --git a/app/components/error_text/error_text.js b/app/components/error_text/error_text.js index 72f88c408..2e3f751c7 100644 --- a/app/components/error_text/error_text.js +++ b/app/components/error_text/error_text.js @@ -12,13 +12,14 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme'; export default class ErrorText extends PureComponent { static propTypes = { + testID: PropTypes.string, error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), textStyle: CustomPropTypes.Style, theme: PropTypes.object.isRequired, }; render() { - const {error, textStyle, theme} = this.props; + const {testID, error, textStyle, theme} = this.props; if (!error) { return null; } @@ -29,7 +30,7 @@ export default class ErrorText extends PureComponent { if (intl) { return ( {error.message || error} diff --git a/app/components/error_text/error_text.test.js b/app/components/error_text/error_text.test.js index ddcb88fcf..75fc44017 100644 --- a/app/components/error_text/error_text.test.js +++ b/app/components/error_text/error_text.test.js @@ -9,6 +9,7 @@ import ErrorText from './error_text.js'; describe('ErrorText', () => { const baseProps = { + testID: 'error.text', textStyle: { fontSize: 14, marginHorizontal: 15, diff --git a/app/components/post_draft/__snapshots__/post_draft.test.js.snap b/app/components/post_draft/__snapshots__/post_draft.test.js.snap index 7e1a89465..210172a93 100644 --- a/app/components/post_draft/__snapshots__/post_draft.test.js.snap +++ b/app/components/post_draft/__snapshots__/post_draft.test.js.snap @@ -384,6 +384,7 @@ exports[`PostDraft Should render the DraftInput 1`] = ` "height": 44, } } + testID="post_draft.quick_actions" > +`; diff --git a/app/components/post_draft/archived/archived.js b/app/components/post_draft/archived/archived.js index e2f3184f8..323ebd5b0 100644 --- a/app/components/post_draft/archived/archived.js +++ b/app/components/post_draft/archived/archived.js @@ -14,6 +14,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; export default class Archived extends PureComponent { static propTypes = { + testID: PropTypes.string, deactivated: PropTypes.bool, rootId: PropTypes.string, selectPenultimateChannel: PropTypes.func.isRequired, @@ -46,12 +47,12 @@ export default class Archived extends PureComponent { }; render() { - const {theme} = this.props; + const {testID, theme} = this.props; const style = getStyleSheet(theme); return ( { + const baseProps = { + selectPenultimateChannel: jest.fn(), + testID: 'post_draft.archived', + deactivated: false, + rootId: 'root-id', + teamId: 'team-id', + theme: Preferences.THEMES.default, + }; + + test('should match snapshot', () => { + const wrapper = shallowWithIntl(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); +}); diff --git a/app/components/post_draft/draft_input/draft_input.js b/app/components/post_draft/draft_input/draft_input.js index e7375aa24..f56b7591c 100644 --- a/app/components/post_draft/draft_input/draft_input.js +++ b/app/components/post_draft/draft_input/draft_input.js @@ -409,6 +409,9 @@ export default class DraftInput extends PureComponent { rootId, theme, } = this.props; + const postInputTestID = `${testID}.post.input`; + const quickActionsTestID = `${testID}.quick_actions`; + const sendActionTestID = `${testID}.send_action`; const style = getStyleSheet(theme); return ( @@ -435,6 +438,7 @@ export default class DraftInput extends PureComponent { disableScrollViewPanResponder={true} > { const { + testID, accessoriesContainerID, canPost, channelId, @@ -71,8 +73,11 @@ export default class PostDraft extends PureComponent { } = this.props; if (channelIsArchived || deactivatedChannel) { + const archivedTestID = `${testID}.archived`; + return ( + ); } const draftInput = ( { const baseProps = { + testID: 'post_draft', canPost: true, channelId: 'channel-id', channelIsArchived: false, diff --git a/app/components/post_draft/post_input/post_input.js b/app/components/post_draft/post_input/post_input.js index a337a32f6..84d8c3f44 100644 --- a/app/components/post_draft/post_input/post_input.js +++ b/app/components/post_draft/post_input/post_input.js @@ -27,6 +27,7 @@ export default class PostInput extends PureComponent { }; static propTypes = { + testID: PropTypes.string, channelDisplayName: PropTypes.string, channelId: PropTypes.string.isRequired, cursorPositionEvent: PropTypes.string, @@ -267,7 +268,7 @@ export default class PostInput extends PureComponent { render() { const {formatMessage} = this.context.intl; - const {channelDisplayName, isLandscape, theme} = this.props; + const {testID, channelDisplayName, isLandscape, theme} = this.props; const style = getStyleSheet(theme); const placeholder = this.getPlaceHolder(); let maxHeight = 150; @@ -278,7 +279,7 @@ export default class PostInput extends PureComponent { return ( { const baseProps = { + testID: 'post_draft.post.input', channelDisplayName: 'Test Channel', channelId: 'channel-id', cursorPositionEvent: '', diff --git a/app/components/post_draft/quick_actions/__snapshots__/quick_actions.test.js.snap b/app/components/post_draft/quick_actions/__snapshots__/quick_actions.test.js.snap new file mode 100644 index 000000000..f85fd79f2 --- /dev/null +++ b/app/components/post_draft/quick_actions/__snapshots__/quick_actions.test.js.snap @@ -0,0 +1,195 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`QuickActions should match snapshot 1`] = ` + + + + + + + +`; diff --git a/app/components/post_draft/quick_actions/camera_quick_action/__snapshots__/camera_quick_action.test.js.snap b/app/components/post_draft/quick_actions/camera_quick_action/__snapshots__/camera_quick_action.test.js.snap index 0087b8123..aa0ff1db0 100644 --- a/app/components/post_draft/quick_actions/camera_quick_action/__snapshots__/camera_quick_action.test.js.snap +++ b/app/components/post_draft/quick_actions/camera_quick_action/__snapshots__/camera_quick_action.test.js.snap @@ -10,7 +10,7 @@ exports[`CameraButton should match snapshot 1`] = ` "padding": 10, } } - testID="post_draft.camera_quick_action" + testID="post_draft.quick_actions.camera_action" type="opacity" > ({ describe('CameraButton', () => { const formatMessage = jest.fn(); const baseProps = { + testID: 'post_draft.quick_actions.camera_action', fileCount: 0, maxFileCount: 5, onShowFileMaxWarning: jest.fn(), diff --git a/app/components/post_draft/quick_actions/camera_quick_action/index.js b/app/components/post_draft/quick_actions/camera_quick_action/index.js index 92b21f3df..07184bfbd 100644 --- a/app/components/post_draft/quick_actions/camera_quick_action/index.js +++ b/app/components/post_draft/quick_actions/camera_quick_action/index.js @@ -22,6 +22,7 @@ import {changeOpacity} from '@utils/theme'; export default class CameraQuickAction extends PureComponent { static propTypes = { + testID: PropTypes.string, disabled: PropTypes.bool, fileCount: PropTypes.number, maxFileCount: PropTypes.number, @@ -147,17 +148,17 @@ export default class CameraQuickAction extends PureComponent { }; render() { - const {disabled, theme} = this.props; - const testID = disabled ? - 'post_draft.camera_quick_action.disabled' : - 'post_draft.camera_quick_action'; + const {testID, disabled, theme} = this.props; + const actionTestID = disabled ? + `${testID}.disabled` : + testID; const color = disabled ? changeOpacity(theme.centerChannelColor, 0.16) : changeOpacity(theme.centerChannelColor, 0.64); return ( { const formatMessage = jest.fn(); const baseProps = { + testID: 'post_draft.quick_actions.file_action', fileCount: 0, maxFileCount: 5, onShowFileMaxWarning: jest.fn(), diff --git a/app/components/post_draft/quick_actions/file_quick_action/index.js b/app/components/post_draft/quick_actions/file_quick_action/index.js index e504b5b5e..9187e0340 100644 --- a/app/components/post_draft/quick_actions/file_quick_action/index.js +++ b/app/components/post_draft/quick_actions/file_quick_action/index.js @@ -20,6 +20,7 @@ const ShareExtension = NativeModules.MattermostShare; export default class FileQuickAction extends PureComponent { static propTypes = { + testID: PropTypes.string, disabled: PropTypes.bool, fileCount: PropTypes.number, maxFileCount: PropTypes.number, @@ -139,17 +140,17 @@ export default class FileQuickAction extends PureComponent { }; render() { - const {disabled, theme} = this.props; - const testID = disabled ? - 'post_draft.file_quick_action.disabled' : - 'post_draft.file_quick_action'; + const {testID, disabled, theme} = this.props; + const actionTestID = disabled ? + `${testID}.disabled` : + testID; const color = disabled ? changeOpacity(theme.centerChannelColor, 0.16) : changeOpacity(theme.centerChannelColor, 0.64); return ( ({ describe('ImageQuickAction', () => { const formatMessage = jest.fn(); const baseProps = { + testID: 'post_draft.quick_actions.image_action', fileCount: 0, maxFileCount: 5, onShowFileMaxWarning: jest.fn(), diff --git a/app/components/post_draft/quick_actions/image_quick_action/index.js b/app/components/post_draft/quick_actions/image_quick_action/index.js index ac7067ccf..e0ab98829 100644 --- a/app/components/post_draft/quick_actions/image_quick_action/index.js +++ b/app/components/post_draft/quick_actions/image_quick_action/index.js @@ -17,6 +17,7 @@ import {changeOpacity} from '@utils/theme'; export default class ImageQuickAction extends PureComponent { static propTypes = { + testID: PropTypes.string, disabled: PropTypes.bool, fileCount: PropTypes.number, maxFileCount: PropTypes.number, @@ -154,17 +155,17 @@ export default class ImageQuickAction extends PureComponent { }; render() { - const {disabled, theme} = this.props; - const testID = disabled ? - 'post_draft.image_quick_action.disabled' : - 'post_draft.image_quick_action'; + const {testID, disabled, theme} = this.props; + const actionTestID = disabled ? + `${testID}.disabled` : + testID; const color = disabled ? changeOpacity(theme.centerChannelColor, 0.16) : changeOpacity(theme.centerChannelColor, 0.64); return ( + + +`; diff --git a/app/components/post_draft/quick_actions/input_quick_action/index.js b/app/components/post_draft/quick_actions/input_quick_action/index.js index 5eb0f2ca5..31857d324 100644 --- a/app/components/post_draft/quick_actions/input_quick_action/index.js +++ b/app/components/post_draft/quick_actions/input_quick_action/index.js @@ -11,6 +11,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; export default class InputQuickAction extends PureComponent { static propTypes = { + testID: PropTypes.string, disabled: PropTypes.bool, inputType: PropTypes.oneOf(['at', 'slash']).isRequired, onTextChange: PropTypes.func.isRequired, @@ -56,11 +57,15 @@ export default class InputQuickAction extends PureComponent { } render() { - const {disabled, theme} = this.props; + const {testID, disabled, theme} = this.props; + const actionTestID = disabled ? + `${testID}.disabled` : + testID; const style = getStyleSheet(theme); return ( { + const baseProps = { + onTextChange: jest.fn(), + testID: 'post_draft.quick_actions.input_action', + disabled: false, + inputType: 'at', + theme: Preferences.THEMES.default, + value: 'value', + }; + + test('should match snapshot', () => { + const wrapper = shallow(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); +}); diff --git a/app/components/post_draft/quick_actions/quick_actions.js b/app/components/post_draft/quick_actions/quick_actions.js index 5bb0404f6..03ed712cb 100644 --- a/app/components/post_draft/quick_actions/quick_actions.js +++ b/app/components/post_draft/quick_actions/quick_actions.js @@ -15,6 +15,7 @@ import InputAction from './input_quick_action'; export default class QuickActions extends PureComponent { static propTypes = { + testID: PropTypes.string, canUploadFiles: PropTypes.bool, fileCount: PropTypes.number, inputEventType: PropTypes.string.isRequired, @@ -64,10 +65,16 @@ export default class QuickActions extends PureComponent { render() { const { + testID, canUploadFiles, fileCount, theme, } = this.props; + const atInputActionTestID = `${testID}.at_input_action`; + const slashInputActionTestID = `${testID}.slash_input_action`; + const fileActionTestID = `${testID}.file_action`; + const imageActionTestID = `${testID}.image_action`; + const cameraActionTestID = `${testID}.camera_action`; const uploadProps = { disabled: !canUploadFiles, fileCount, @@ -77,8 +84,12 @@ export default class QuickActions extends PureComponent { }; return ( - + - - - + + + ); } diff --git a/app/components/post_draft/quick_actions/quick_actions.test.js b/app/components/post_draft/quick_actions/quick_actions.test.js new file mode 100644 index 000000000..ea39be784 --- /dev/null +++ b/app/components/post_draft/quick_actions/quick_actions.test.js @@ -0,0 +1,27 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {shallow} from 'enzyme'; + +import Preferences from '@mm-redux/constants/preferences'; + +import QuickActions from './quick_actions'; + +describe('QuickActions', () => { + const baseProps = { + onTextChange: jest.fn(), + testID: 'post_draft.quick_actions', + canUploadFiles: true, + fileCount: 1, + inputEventType: 'input-event-type', + maxFileSize: 10, + theme: Preferences.THEMES.default, + }; + + test('should match snapshot', () => { + const wrapper = shallow(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); +}); \ No newline at end of file diff --git a/app/components/post_draft/read_only/__snapshots__/read_only.test.js.snap b/app/components/post_draft/read_only/__snapshots__/read_only.test.js.snap index 026c1fbe6..ab58ec7b0 100644 --- a/app/components/post_draft/read_only/__snapshots__/read_only.test.js.snap +++ b/app/components/post_draft/read_only/__snapshots__/read_only.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PostDraft ReadOnly Should match snapshot 1`] = ` +exports[`PostDraft ReadOnly should match snapshot 1`] = ` { +const ReadOnlyChannnel = ({testID, theme}: ReadOnlyProps): ReactNode => { const style = getStyle(theme); return ( { style={style.background} > { - test('Should match snapshot', () => { - const wrapper = shallowWithIntl( - , - ); + const baseProps = { + testID: 'post_draft.read_only', + theme: Preferences.THEMES.default, + }; - expect(wrapper).toMatchSnapshot(); + test('should match snapshot', () => { + const wrapper = shallowWithIntl(); + + expect(wrapper.getElement()).toMatchSnapshot(); }); }); diff --git a/app/components/post_draft/send_action/__snapshots__/send.test.js.snap b/app/components/post_draft/send_action/__snapshots__/send.test.js.snap index 3824b8b99..eb284b1a4 100644 --- a/app/components/post_draft/send_action/__snapshots__/send.test.js.snap +++ b/app/components/post_draft/send_action/__snapshots__/send.test.js.snap @@ -8,7 +8,7 @@ exports[`SendAction should change theme backgroundColor to 0.3 opacity 1`] = ` "paddingRight": 8, } } - testID="post_draft.send.button.disabled" + testID="post_draft.send_action.send.button.disabled" > @@ -32,7 +34,7 @@ function SendButton(props) { return ( { const baseProps = { + testID: 'post_draft.send_action', theme: Preferences.THEMES.default, handleSendMessage: jest.fn(), disabled: false, diff --git a/app/components/post_header/__snapshots__/post_header.test.js.snap b/app/components/post_header/__snapshots__/post_header.test.js.snap index a6afb9dd0..0fa554399 100644 --- a/app/components/post_header/__snapshots__/post_header.test.js.snap +++ b/app/components/post_header/__snapshots__/post_header.test.js.snap @@ -515,6 +515,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB "justifyContent": "flex-end", } } + testID="post_header.reply" > + { const { + testID, highlightPinnedOrFlagged, highlightPostId, isSearchResult, @@ -360,6 +362,7 @@ export default class PostList extends PureComponent { const postId = item; return ( + + + } + containerStyle={ + Object { + "backgroundColor": "#ffffff", + "flex": 1, + "paddingBottom": 0, + "paddingTop": 0, + } + } + disableFullscreenUI={true} + editable={true} + enablesReturnKeyAutomatically={true} + inputContainerStyle={ + Object { + "backgroundColor": undefined, + "borderRadius": 2, + "height": 10, + } + } + inputStyle={ + Object { + "backgroundColor": "transparent", + "color": "#fff", + "fontSize": 14, + "height": 10, + "marginLeft": 4, + "marginTop": 0, + "paddingTop": 0, + } + } + keyboardAppearance="keyboard-appearance" + keyboardType="keyboard-type" + leftIconContainerStyle={ + Object { + "marginLeft": 4, + "width": 30, + } + } + onBlur={[Function]} + onCancel={[Function]} + onChangeText={[Function]} + onClear={[Function]} + onFocus={[Function]} + onSelectionChange={[Function]} + onSubmitEditing={[Function]} + placeholder="placeholder" + placeholderTextColor="#000000" + platform="ios" + returnKeyType="return-key-type" + searchIcon={ + + } + selectionColor="#dddddd" + showCancel={true} + testID="search_bar.search.input" + underlineColorAndroid="transparent" + value="value" + /> + + +`; diff --git a/app/components/search_bar/index.js b/app/components/search_bar/index.js index 7198e4ea6..590b29a3c 100644 --- a/app/components/search_bar/index.js +++ b/app/components/search_bar/index.js @@ -25,6 +25,7 @@ const LEFT_COMPONENT_INITIAL_POSITION = Platform.OS === 'ios' ? 7 : 0; export default class Search extends PureComponent { static propTypes = { + testID: PropTypes.string, onBlur: PropTypes.func, onFocus: PropTypes.func, onSearchButtonPress: PropTypes.func, @@ -214,7 +215,10 @@ export default class Search extends PureComponent { } render() { - const {backgroundColor, inputHeight, inputStyle, placeholderTextColor, tintColorSearch, cancelButtonStyle, tintColorDelete, titleCancelColor, searchBarRightMargin, containerHeight} = this.props; + const {testID, backgroundColor, inputHeight, inputStyle, placeholderTextColor, tintColorSearch, cancelButtonStyle, tintColorDelete, titleCancelColor, searchBarRightMargin, containerHeight} = this.props; + const searchClearButtonTestID = `${testID}.search.clear.button`; + const searchBackButtonTestID = `${testID}.search.back.button`; + const searchInputTestID = `${testID}.search.input`; const searchBarStyle = getSearchBarStyle( backgroundColor, cancelButtonStyle, @@ -236,6 +240,7 @@ export default class Search extends PureComponent { if (Platform.OS === 'ios') { clearIcon = ( + {((this.props.leftComponent) ? { + const baseProps = { + onBlur: jest.fn, + onFocus: jest.fn, + onSearchButtonPress: jest.fn, + onChangeText: jest.fn, + onCancelButtonPress: jest.fn, + onSelectionChange: jest.fn, + testID: 'search_bar', + backgroundColor: '#ffffff', + placeholderTextColor: '#000000', + titleCancelColor: '#aaaaaa', + tintColorSearch: '#bbbbbb', + tintColorDelete: '#cccccc', + selectionColor: '#dddddd', + inputStyle: {}, + containerStyle: {}, + cancelButtonStyle: {}, + autoFocus: true, + placeholder: 'placeholder', + cancelTitle: 'cancel title', + returnKeyType: 'return-key-type', + keyboardType: 'keyboard-type', + autoCapitalize: 'auto-capitalize', + inputHeight: 10, + editable: true, + blurOnSubmit: true, + keyboardShouldPersist: true, + value: 'value', + keyboardAppearance: 'keyboard-appearance', + showArrow: true, + searchBarRightMargin: 5, + searchIconSize: 1, + backArrowSize: 1, + deleteIconSize: 1, + showCancel: true, + containerHeight: 20, + }; + + test('should match snapshot', () => { + const wrapper = shallowWithIntl(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); +}); diff --git a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap index 77f57baf1..bcbe95d59 100644 --- a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap +++ b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap @@ -16,6 +16,7 @@ exports[`ChannelItem should match snapshot 1`] = ` undefined, ] } + testID="main.sidebar.channels_list.list.channel_item" > display_name @@ -118,6 +119,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you undefined, ] } + testID="main.sidebar.channels_list.list.channel_item" > {displayname} (you) @@ -230,6 +232,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you undefined, ] } + testID="main.sidebar.channels_list.list.channel_item" > {displayname} (you) @@ -342,6 +345,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh undefined, ] } + testID="main.sidebar.channels_list.list.channel_item" > display_name @@ -454,6 +458,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes undefined, ] } + testID="main.sidebar.channels_list.list.channel_item" > display_name @@ -556,6 +561,7 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe undefined, ] } + testID="main.sidebar.channels_list.list.channel_item" > display_name @@ -658,6 +664,7 @@ exports[`ChannelItem should match snapshot for isManualUnread 1`] = ` undefined, ] } + testID="main.sidebar.channels_list.list.channel_item" > display_name @@ -764,6 +771,7 @@ exports[`ChannelItem should match snapshot with draft 1`] = ` undefined, ] } + testID="main.sidebar.channels_list.list.channel_item" > display_name @@ -868,6 +876,7 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = ` }, ] } + testID="main.sidebar.channels_list.list.channel_item" > display_name diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.js index fd530390a..a2dd6621a 100644 --- a/app/components/sidebars/main/channels_list/channel_item/channel_item.js +++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.js @@ -18,6 +18,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; export default class ChannelItem extends PureComponent { static propTypes = { + testID: PropTypes.string, channelId: PropTypes.string.isRequired, channel: PropTypes.object, currentChannelId: PropTypes.string.isRequired, @@ -61,6 +62,7 @@ export default class ChannelItem extends PureComponent { render() { const { + testID, channelId, currentChannelId, displayName, @@ -165,12 +167,17 @@ export default class ChannelItem extends PureComponent { /> ); + const displayNameTestID = `${testID}.display_name`; + return ( - + {extraBorder} {icon} @@ -178,7 +185,7 @@ export default class ChannelItem extends PureComponent { style={[style.text, extraTextStyle]} ellipsizeMode='tail' numberOfLines={1} - testID='channel_item.display_name' + testID={displayNameTestID} > {channelDisplayName} diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js index 7a9ba72c3..55233e017 100644 --- a/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js +++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js @@ -7,7 +7,7 @@ import {TouchableHighlight} from 'react-native'; import Preferences from '@mm-redux/constants/preferences'; -import ChannelItem from './channel_item.js'; +import ChannelItem from './channel_item'; jest.useFakeTimers(); jest.mock('react-intl'); @@ -22,6 +22,7 @@ describe('ChannelItem', () => { }; const baseProps = { + testID: 'main.sidebar.channels_list.list.channel_item', channelId: 'channel_id', channel, currentChannelId: 'current_channel_id', diff --git a/app/components/sidebars/main/channels_list/channels_list.js b/app/components/sidebars/main/channels_list/channels_list.js index e12bb70fb..15b7c24d8 100644 --- a/app/components/sidebars/main/channels_list/channels_list.js +++ b/app/components/sidebars/main/channels_list/channels_list.js @@ -27,6 +27,7 @@ let FilteredList = null; export default class ChannelsList extends PureComponent { static propTypes = { + testID: PropTypes.string, onJoinChannel: PropTypes.func.isRequired, onSearchEnds: PropTypes.func.isRequired, onSearchStart: PropTypes.func.isRequired, @@ -92,15 +93,18 @@ export default class ChannelsList extends PureComponent { render() { const {intl} = this.context; - const {onShowTeams, theme} = this.props; - + const {testID, onShowTeams, theme} = this.props; const {searching, term} = this.state; const styles = getStyleSheet(theme); + const filteredListTestID = `${testID}.filtered_list`; + const listTestID = `${testID}.list`; + const searchBarTestID = `${testID}.search_bar`; let list; if (searching) { list = ( @@ -128,6 +133,7 @@ export default class ChannelsList extends PureComponent { style={styles.searchContainer} > {title} diff --git a/app/components/sidebars/main/channels_list/filtered_list/__snapshots__/filtered_list.test.js.snap b/app/components/sidebars/main/channels_list/filtered_list/__snapshots__/filtered_list.test.js.snap new file mode 100644 index 000000000..694e31876 --- /dev/null +++ b/app/components/sidebars/main/channels_list/filtered_list/__snapshots__/filtered_list.test.js.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ChannelsList FilteredList should match snapshot 1`] = ` + +`; diff --git a/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js b/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js index afcf9237f..0430e11ac 100644 --- a/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js +++ b/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js @@ -24,6 +24,7 @@ const VIEWABILITY_CONFIG = ListTypes.VISIBILITY_CONFIG_DEFAULTS; class FilteredList extends Component { static propTypes = { + testID: PropTypes.string, actions: PropTypes.shape({ getProfilesInTeam: PropTypes.func.isRequired, makeGroupMessageVisibleIfNecessary: PropTypes.func.isRequired, @@ -339,8 +340,12 @@ class FilteredList extends Component { keyExtractor = (item) => item.id || item; renderItem = ({item}) => { + const {testID} = this.props; + const channelItemTestID = `${testID}.channel_item`; + return ( { + const baseProps = { + actions: { + getProfilesInTeam: jest.fn(), + makeGroupMessageVisibleIfNecessary: jest.fn(), + searchChannels: jest.fn(), + searchProfiles: jest.fn(), + }, + onSelectChannel: jest.fn(), + testID: 'main.sidebar.channels_list.filtered_list', + channels: {}, + currentTeam: {}, + currentUserId: 'current-user-id', + currentChannel: {}, + groupChannelMemberDetails: {}, + teammateNameDisplay: 'teammate-name-display', + otherChannels: [], + archivedChannels: [], + profiles: {}, + teamProfiles: {}, + searchOrder: [], + pastDirectMessages: [], + restrictDms: false, + statuses: {}, + styles: {}, + term: 'term', + theme: Preferences.THEMES.default, + isLandscape: false, + }; + + test('should match snapshot', () => { + const wrapper = shallowWithIntl(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); +}); diff --git a/app/components/sidebars/main/channels_list/list/__snapshots__/list.test.js.snap b/app/components/sidebars/main/channels_list/list/__snapshots__/list.test.js.snap new file mode 100644 index 000000000..d0f3f5a3c --- /dev/null +++ b/app/components/sidebars/main/channels_list/list/__snapshots__/list.test.js.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ChannelsList List should match snapshot 1`] = ` + + + +`; diff --git a/app/components/sidebars/main/channels_list/list/list.js b/app/components/sidebars/main/channels_list/list/list.js index a4cb9d478..e20ffb533 100644 --- a/app/components/sidebars/main/channels_list/list/list.js +++ b/app/components/sidebars/main/channels_list/list/list.js @@ -38,6 +38,7 @@ let UnreadIndicator = null; export default class List extends PureComponent { static propTypes = { + testID: PropTypes.string, canJoinPublicChannels: PropTypes.bool.isRequired, canCreatePrivateChannels: PropTypes.bool.isRequired, canCreatePublicChannels: PropTypes.bool.isRequired, @@ -311,10 +312,12 @@ export default class List extends PureComponent { }; renderItem = ({item}) => { - const {favoriteChannelIds, unreadChannelIds} = this.props; + const {testID, favoriteChannelIds, unreadChannelIds} = this.props; + const channelItemTestID = `${testID}.channel_item`; return ( diff --git a/app/components/sidebars/main/channels_list/list/list.test.js b/app/components/sidebars/main/channels_list/list/list.test.js new file mode 100644 index 000000000..55e510311 --- /dev/null +++ b/app/components/sidebars/main/channels_list/list/list.test.js @@ -0,0 +1,31 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {shallow} from 'enzyme'; + +import Preferences from '@mm-redux/constants/preferences'; + +import List from './list'; + +describe('ChannelsList List', () => { + const baseProps = { + onSelectChannel: jest.fn(), + testID: 'main.sidebar.channels_list.list', + canJoinPublicChannels: true, + canCreatePrivateChannels: true, + canCreatePublicChannels: true, + favoriteChannelIds: [], + unreadChannelIds: [], + styles: {}, + theme: Preferences.THEMES.default, + orderedChannelIds: [], + isLandscape: false, + }; + + test('should match snapshot', () => { + const wrapper = shallow(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); +}); diff --git a/app/components/sidebars/main/main_sidebar_base.js b/app/components/sidebars/main/main_sidebar_base.js index e2aeeb6c9..2488bbc38 100644 --- a/app/components/sidebars/main/main_sidebar_base.js +++ b/app/components/sidebars/main/main_sidebar_base.js @@ -230,6 +230,7 @@ export default class MainSidebarBase extends Component { style={style.swiperContent} > + + +`; diff --git a/app/screens/add_reaction/add_reaction.test.js b/app/screens/add_reaction/add_reaction.test.js new file mode 100644 index 000000000..937571867 --- /dev/null +++ b/app/screens/add_reaction/add_reaction.test.js @@ -0,0 +1,21 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {shallowWithIntl} from 'test/intl-test-helper'; + +import AddReaction from './index'; + +describe('AddReaction', () => { + const baseProps = { + onEmojiPress: jest.fn(), + componentId: 'component-id', + closeButton: {}, + }; + + test('should match snapshot', () => { + const wrapper = shallowWithIntl(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); +}); diff --git a/app/screens/channel/channel.android.js b/app/screens/channel/channel.android.js index 3ad15c658..7c0c0494c 100644 --- a/app/screens/channel/channel.android.js +++ b/app/screens/channel/channel.android.js @@ -74,6 +74,7 @@ export default class ChannelAndroid extends ChannelBase { diff --git a/app/screens/channel/channel_post_list/channel_post_list.js b/app/screens/channel/channel_post_list/channel_post_list.js index 9c6c0abb8..4d7aef47d 100644 --- a/app/screens/channel/channel_post_list/channel_post_list.js +++ b/app/screens/channel/channel_post_list/channel_post_list.js @@ -192,6 +192,7 @@ export default class ChannelPostList extends PureComponent { } else { component = ( - + ); } else { displayError = ( - + ); diff --git a/app/screens/edit_profile/edit_profile.js b/app/screens/edit_profile/edit_profile.js index f08c19639..08a00f5a7 100644 --- a/app/screens/edit_profile/edit_profile.js +++ b/app/screens/edit_profile/edit_profile.js @@ -583,6 +583,7 @@ export default class EditProfile extends PureComponent { @@ -592,7 +593,10 @@ export default class EditProfile extends PureComponent { } return ( - + @@ -148,7 +149,10 @@ export default class ForgotPassword extends PureComponent { ); } return ( - + + {error && ( diff --git a/app/screens/login/login.js b/app/screens/login/login.js index 31c36f095..cba786010 100644 --- a/app/screens/login/login.js +++ b/app/screens/login/login.js @@ -338,7 +338,7 @@ export default class Login extends PureComponent { proceed = ( - + diff --git a/app/screens/select_server/select_server.test.js b/app/screens/select_server/select_server.test.js index 443f3751e..f23e6650b 100644 --- a/app/screens/select_server/select_server.test.js +++ b/app/screens/select_server/select_server.test.js @@ -32,7 +32,7 @@ describe('SelectServer', () => { const button = getByText('Connect'); fireEvent.press(button); - await waitFor(() => expect(getByTestId('error.text')).toBeTruthy()); + await waitFor(() => expect(getByTestId('select_server.error.text')).toBeTruthy()); expect(getByText('Please enter a valid server URL')).toBeTruthy(); }); @@ -41,13 +41,13 @@ describe('SelectServer', () => { , ); - const urlInput = getByTestId('server_url.input'); + const urlInput = getByTestId('select_server.server_url.input'); fireEvent.changeText(urlInput, ' '); const button = getByText('Connect'); fireEvent.press(button); - await waitFor(() => expect(getByTestId('error.text')).toBeTruthy()); + await waitFor(() => expect(getByTestId('select_server.error.text')).toBeTruthy()); expect(getByText('Please enter a valid server URL')).toBeTruthy(); }); @@ -56,13 +56,13 @@ describe('SelectServer', () => { , ); - const urlInput = getByTestId('server_url.input'); + const urlInput = getByTestId('select_server.server_url.input'); fireEvent.changeText(urlInput, 'ht://invalid:8065'); const button = getByText('Connect'); fireEvent.press(button); - await waitFor(() => expect(getByTestId('error.text')).toBeTruthy()); + await waitFor(() => expect(getByTestId('select_server.error.text')).toBeTruthy()); expect(getByText('URL must start with http:// or https://')).toBeTruthy(); }); @@ -71,13 +71,13 @@ describe('SelectServer', () => { , ); - const urlInput = getByTestId('server_url.input'); + const urlInput = getByTestId('select_server.server_url.input'); fireEvent.changeText(urlInput, 'http://localhost:8065'); const button = getByText('Connect'); fireEvent.press(button); - expect(queryByTestId('error.text')).toBeNull(); + expect(queryByTestId('select_server.error.text')).toBeNull(); await waitFor(() => expect(getByText('Connecting...')).toBeTruthy()); }); }); \ No newline at end of file diff --git a/app/screens/selector_screen/__snapshots__/selector_screen.test.js.snap b/app/screens/selector_screen/__snapshots__/selector_screen.test.js.snap index 6487fab2e..19e531bc2 100644 --- a/app/screens/selector_screen/__snapshots__/selector_screen.test.js.snap +++ b/app/screens/selector_screen/__snapshots__/selector_screen.test.js.snap @@ -17,6 +17,7 @@ exports[`SelectorScreen should match snapshot for channels 1`] = ` "paddingLeft": 8, } } + testID="selector.screen" > - + + + + + + + +`; diff --git a/app/screens/settings/timezone/select_timezone/select_timezone.js b/app/screens/settings/timezone/select_timezone/select_timezone.js index d5ed58395..bcf093457 100644 --- a/app/screens/settings/timezone/select_timezone/select_timezone.js +++ b/app/screens/settings/timezone/select_timezone/select_timezone.js @@ -108,12 +108,14 @@ export default class Timezone extends PureComponent { return ( { + const baseProps = { + onBack: jest.fn(), + selectedTimezone: 'selected-timezone', + initialScrollIndex: 1, + timezones: [], + theme: Preferences.THEMES.default, + isLandscape: false, + }; + + test('should match snapshot', () => { + const wrapper = shallowWithIntl(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); +}); diff --git a/app/screens/thread/__snapshots__/thread.ios.test.js.snap b/app/screens/thread/__snapshots__/thread.ios.test.js.snap index d39023eaa..112aa801b 100644 --- a/app/screens/thread/__snapshots__/thread.ios.test.js.snap +++ b/app/screens/thread/__snapshots__/thread.ios.test.js.snap @@ -23,6 +23,7 @@ exports[`thread should match snapshot, has root post 1`] = ` "paddingBottom": 0, } } + testID="thread.screen" > } scrollViewNativeID="threadPostList" + testID="thread.post_list" /> @@ -58,6 +60,7 @@ exports[`thread should match snapshot, has root post 1`] = ` registerTypingAnimation={[Function]} rootId="root_id" scrollViewNativeID="threadPostList" + testID="thread.post_draft" valueEvent="onThreadTextBoxValueChange" /> } scrollViewNativeID="threadPostList" + testID="thread.post_list" /> `; @@ -154,6 +158,7 @@ exports[`thread should match snapshot, render footer 2`] = ` } renderFooter={null} scrollViewNativeID="threadPostList" + testID="thread.post_list" /> `; diff --git a/app/screens/thread/thread.android.js b/app/screens/thread/thread.android.js index cf208b667..c4e3d2d76 100644 --- a/app/screens/thread/thread.android.js +++ b/app/screens/thread/thread.android.js @@ -30,8 +30,12 @@ export default class ThreadAndroid extends ThreadBase { if (this.hasRootPost()) { content = ( <> - + - + { + return element(by.id(`${screenPrefix}${this.testID.cameraActionSuffix}`)); + } - toBeVisible = async (options = {disabled: false}) => { - if (options.disabled) { - await expect(this.cameraQuickActionDisabled).toBeVisible(); - return this.cameraQuickActionDisabled; - } - - await expect(this.cameraQuickAction).toBeVisible(); - return this.cameraQuickAction; + getCameraQuickActionDisabled = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.cameraActionDisabledSuffix}`)); } } diff --git a/detox/e2e/support/ui/component/edit_channel_info.js b/detox/e2e/support/ui/component/edit_channel_info.js new file mode 100644 index 000000000..f3c7c0b6e --- /dev/null +++ b/detox/e2e/support/ui/component/edit_channel_info.js @@ -0,0 +1,17 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class EditChannelInfo { + testID = { + nameInput: 'edit_channel_info.name.input', + purposeInput: 'edit_channel_info.purpose.input', + headerInput: 'edit_channel_info.header.input', + } + + nameInput = element(by.id(this.testID.nameInput)); + purposeInput = element(by.id(this.testID.purposeInput)); + headerInput = element(by.id(this.testID.headerInput)); +} + +const editChannelInfo = new EditChannelInfo(); +export default editChannelInfo; diff --git a/detox/e2e/support/ui/component/file_quick_action.js b/detox/e2e/support/ui/component/file_quick_action.js index 410e1bdad..86f9d6630 100644 --- a/detox/e2e/support/ui/component/file_quick_action.js +++ b/detox/e2e/support/ui/component/file_quick_action.js @@ -3,21 +3,16 @@ class FileQuickAction { testID = { - fileQuickAction: 'post_draft.file_quick_action', - fileQuickActionDisabled: 'post_draft.file_quick_action.disabled', + fileActionSuffix: 'post_draft.quick_actions.file_action', + fileActionDisabledSuffix: 'post_draft.quick_actions.file_action.disabled', } - fileQuickAction = element(by.id(this.testID.fileQuickAction)); - fileQuickActionDisabled = element(by.id(this.testID.fileQuickActionDisabled)); + getFileQuickAction = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.fileActionSuffix}`)); + } - toBeVisible = async (options = {disabled: false}) => { - if (options.disabled) { - await expect(this.fileQuickActionDisabled).toBeVisible(); - return this.fileQuickActionDisabled; - } - - await expect(this.fileQuickAction).toBeVisible(); - return this.fileQuickAction; + getFileQuickActionDisabled = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.fileActionDisabledSuffix}`)); } } diff --git a/detox/e2e/support/ui/component/image_quick_action.js b/detox/e2e/support/ui/component/image_quick_action.js index b041ffec2..d0c0c89e6 100644 --- a/detox/e2e/support/ui/component/image_quick_action.js +++ b/detox/e2e/support/ui/component/image_quick_action.js @@ -3,21 +3,16 @@ class ImageQuickAction { testID = { - imageQuickAction: 'post_draft.image_quick_action', - imageQuickActionDisabled: 'post_draft.image_quick_action.disabled', + imageActionSuffix: 'post_draft.quick_actions.image_action', + imageActionDisabledSuffix: 'post_draft.quick_actions.image_action.disabled', } - imageQuickAction = element(by.id(this.testID.imageQuickAction)); - imageQuickActionDisabled = element(by.id(this.testID.imageQuickActionDisabled)); + getImageQuickAction = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.imageActionSuffix}`)); + } - toBeVisible = async (options = {disabled: false}) => { - if (options.disabled) { - await expect(this.imageQuickActionDisabled).toBeVisible(); - return this.imageQuickActionDisabled; - } - - await expect(this.imageQuickAction).toBeVisible(); - return this.imageQuickAction; + getImageQuickActionDisabled = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.imageActionDisabledSuffix}`)); } } diff --git a/detox/e2e/support/ui/component/index.js b/detox/e2e/support/ui/component/index.js index 230ff9c3a..3e05b638c 100644 --- a/detox/e2e/support/ui/component/index.js +++ b/detox/e2e/support/ui/component/index.js @@ -3,24 +3,32 @@ import Autocomplete from './autocomplete'; import CameraQuickAction from './camera_quick_action'; +import EditChannelInfo from './edit_channel_info'; import FileQuickAction from './file_quick_action'; import ImageQuickAction from './image_quick_action'; +import InputQuickAction from './input_quick_action'; import MainSidebar from './main_sidebar'; import Post from './post'; import PostDraft from './post_draft'; import PostOptions from './post_options'; +import RecentItem from './recent_item'; +import SearchBar from './search_bar'; import SendButton from './send_button'; import SettingsSidebar from './settings_sidebar'; export { Autocomplete, CameraQuickAction, + EditChannelInfo, FileQuickAction, ImageQuickAction, + InputQuickAction, MainSidebar, Post, PostDraft, PostOptions, + RecentItem, + SearchBar, SendButton, SettingsSidebar, }; diff --git a/detox/e2e/support/ui/component/input_quick_action.js b/detox/e2e/support/ui/component/input_quick_action.js new file mode 100644 index 000000000..1fcc0b9e3 --- /dev/null +++ b/detox/e2e/support/ui/component/input_quick_action.js @@ -0,0 +1,30 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class InputQuickAction { + testID = { + atInputActionSuffix: 'post_draft.quick_actions.at_input_action', + atInputActionDisabledSuffix: 'post_draft.quick_actions.at_input_action.disabled', + slashInputActionSuffix: 'post_draft.quick_actions.slash_input_action', + slashInputActionDisabledSuffix: 'post_draft.quick_actions.slash_input_action.disabled', + } + + getAtInputQuickAction = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.atInputActionSuffix}`)); + } + + getAtInputQuickActionDisabled = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.atInputActionDisabledSuffix}`)); + } + + getSlashInputQuickAction = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.slashInputActionSuffix}`)); + } + + getSlashInputQuickActionDisabled = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.slashInputActionDisabledSuffix}`)); + } +} + +const inputQuickAction = new InputQuickAction(); +export default inputQuickAction; diff --git a/detox/e2e/support/ui/component/main_sidebar.js b/detox/e2e/support/ui/component/main_sidebar.js index d929e50e2..c0fb9e98c 100644 --- a/detox/e2e/support/ui/component/main_sidebar.js +++ b/detox/e2e/support/ui/component/main_sidebar.js @@ -4,16 +4,18 @@ class MainSidebar { testID = { mainSidebar: 'main.sidebar', - channelItemDisplayName: 'channel_item.display_name', - channelsList: 'channels.list', + channelsList: 'main.sidebar.channels_list', + channelItemDisplayName: 'main.sidebar.channels_list.list.channel_item.display_name', + filteredChannelItemDisplayName: 'main.sidebar.channels_list.filtered_list.channel_item.display_name', openMoreChannelsButton: 'action_button_sidebar.channels', openCreatePrivateChannelButton: 'action_button_sidebar.pg', openMoreDirectMessagesButton: 'action_button_sidebar.direct', } mainSidebar = element(by.id(this.testID.mainSidebar)); - channelItemDisplayName = element(by.id(this.testID.channelItemDisplayName)); channelsList = element(by.id(this.testID.channelsList)); + channelItemDisplayName = element(by.id(this.testID.channelItemDisplayName)); + filteredChannelItemDisplayName = element(by.id(this.testID.filteredChannelItemDisplayName)); openMoreChannelsButton = element(by.id(this.testID.openMoreChannelsButton)); openCreatePrivateChannelButton = element(by.id(this.testID.openCreatePrivateChannelButton)); openMoreDirectMessagesButton = element(by.id(this.testID.openMoreDirectMessagesButton)); @@ -30,9 +32,15 @@ class MainSidebar { hasChannelAtIndex = async (index, channelDisplayName) => { await expect( - element(by.id(this.testID.channelItemDisplayName).withAncestor(by.id(this.testID.channelsList))).atIndex(index), + element(by.id(this.testID.channelItemDisplayName)).atIndex(index), ).toHaveText(channelDisplayName); } + + hasFilteredChannelAtIndex = async (index, filteredChannelItemDisplayName) => { + await expect( + element(by.id(this.testID.filteredChannelItemDisplayName)).atIndex(index), + ).toHaveText(filteredChannelItemDisplayName); + } } const mainSidebar = new MainSidebar(); diff --git a/detox/e2e/support/ui/component/post.js b/detox/e2e/support/ui/component/post.js index baae556b4..d78c2ee96 100644 --- a/detox/e2e/support/ui/component/post.js +++ b/detox/e2e/support/ui/component/post.js @@ -4,13 +4,21 @@ class Post { testID = { postPrefix: 'post.', + postHeaderReply: 'post_header.reply', } getPost = (postTypePrefix, postId, text) => { + const postTestID = `${postTypePrefix}${this.testID.postPrefix}${postId}`; if (text) { - return element(by.id(`${postTypePrefix}${this.testID.postPrefix}${postId}`).withDescendant(by.text(text))); + return { + postItem: element(by.id(postTestID).withDescendant(by.text(text))), + postItemHeaderReply: element(by.id(this.testID.postHeaderReply).withAncestor(by.id(postTestID).withDescendant(by.text(text)))), + }; } - return element(by.id(`${postTypePrefix}${this.testID.postPrefix}${postId}`)); + return { + postItem: element(by.id(postTestID)), + postItemHeaderReply: element(by.id(this.testID.postHeaderReply).withAncestor(by.id(postTestID))), + }; } } diff --git a/detox/e2e/support/ui/component/post_draft.js b/detox/e2e/support/ui/component/post_draft.js index 22342d1e9..45f2e787b 100644 --- a/detox/e2e/support/ui/component/post_draft.js +++ b/detox/e2e/support/ui/component/post_draft.js @@ -3,30 +3,26 @@ class PostDraft { testID = { - postDraft: 'post_draft', - postDraftArchived: 'post_draft.archived', - postDraftReadOnly: 'post_draft.archived', - postInput: 'post_draft.post.input', + postDraftSuffix: 'post_draft', + postDraftArchivedSuffix: 'post_draft.archived', + postDraftReadOnlySuffix: 'post_draft.read_only', + postInputSuffix: 'post_draft.post.input', } - postDraft = element(by.id(this.testID.postDraft)); - postDraftArchived = element(by.id(this.testID.postDraftArchived)); - postDraftReadOnly = element(by.id(this.testID.postDraftReadOnly)); - postInput = element(by.id(this.testID.postInput)); + getPostDraft = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.postDraftSuffix}`)); + } - toBeVisible = async (options = {archived: false, readOnly: false}) => { - if (options.archived) { - await expect(this.postDraftArchived).toBeVisible(); - return this.postDraftArchived; - } + getPostDraftArchived = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.postDraftArchivedSuffix}`)); + } - if (options.readOnly) { - await expect(this.postDraftReadOnly).toBeVisible(); - return this.postDraftReadOnly; - } + getPostDraftReadOnly = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.postDraftReadOnlySuffix}`)); + } - await expect(this.postDraft).toBeVisible(); - return this.postDraft; + getPostInput = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.postInputSuffix}`)); } } diff --git a/detox/e2e/support/ui/component/recent_item.js b/detox/e2e/support/ui/component/recent_item.js new file mode 100644 index 000000000..c36440a1d --- /dev/null +++ b/detox/e2e/support/ui/component/recent_item.js @@ -0,0 +1,15 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class RecentItem { + testID = { + recentItemPrefix: 'search.recent_item.', + } + + getRecentItem = (searchTerms) => { + return element(by.id(`${this.testID.recentItemPrefix}${searchTerms}`)); + } +} + +const recentItem = new RecentItem(); +export default recentItem; diff --git a/detox/e2e/support/ui/component/search_bar.js b/detox/e2e/support/ui/component/search_bar.js new file mode 100644 index 000000000..90e6b85b5 --- /dev/null +++ b/detox/e2e/support/ui/component/search_bar.js @@ -0,0 +1,34 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class SearchBar { + testID = { + searchBarSuffix: 'search_bar', + searchInputSuffix: 'search_bar.search.input', + backButtonSuffix: 'search_bar.search.back.button', + clearButtonSuffix: 'search_bar.search.clear.button', + } + + getSearchBar = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.searchBarSuffix}`)); + } + + getSearchInput = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.searchInputSuffix}`)).atIndex(0); + } + + getBackButton = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.backButtonSuffix}`)).atIndex(0); + } + + getCancelButton = (screenPrefix) => { + return element(by.text('Cancel').withAncestor(by.id(`${screenPrefix}${this.testID.searchBarSuffix}`))).atIndex(0); + } + + getClearButton = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.clearButtonSuffix}`)).atIndex(0); + } +} + +const searchBar = new SearchBar(); +export default searchBar; diff --git a/detox/e2e/support/ui/component/send_button.js b/detox/e2e/support/ui/component/send_button.js index ca6205220..466a0f96f 100644 --- a/detox/e2e/support/ui/component/send_button.js +++ b/detox/e2e/support/ui/component/send_button.js @@ -3,21 +3,16 @@ class SendButton { testID = { - sendButton: 'post_draft.send.button', - sendButtonDisabled: 'post_draft.send.button.disabled', + sendButtonSuffix: 'post_draft.send_action.send.button', + sendButtonDisabledSuffix: 'post_draft.send_action.send.button.disabled', } - sendButton = element(by.id(this.testID.sendButton)); - sendButtonDisabled = element(by.id(this.testID.sendButtonDisabled)); + getSendButton = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.sendButtonSuffix}`)); + } - toBeVisible = async (options = {disabled: false}) => { - if (options.disabled) { - await expect(this.sendButtonDisabled).toBeVisible(); - return this.sendButtonDisabled; - } - - await expect(this.sendButton).toBeVisible(); - return this.sendButton; + getSendButtonDisabled = (screenPrefix) => { + return element(by.id(`${screenPrefix}${this.testID.sendButtonDisabledSuffix}`)); } } diff --git a/detox/e2e/support/ui/screen/channel.js b/detox/e2e/support/ui/screen/channel.js index efaad997f..1c3147d86 100644 --- a/detox/e2e/support/ui/screen/channel.js +++ b/detox/e2e/support/ui/screen/channel.js @@ -5,6 +5,7 @@ import { CameraQuickAction, FileQuickAction, ImageQuickAction, + InputQuickAction, MainSidebar, PostDraft, PostOptions, @@ -20,6 +21,7 @@ import { class ChannelScreen { testID = { + channelScreenPrefix: 'channel.', channelScreen: 'channel.screen', mainSidebarDrawerButton: 'main_sidebar_drawer.button', channelIntro: 'channel_intro.beginning.text', @@ -38,25 +40,29 @@ class ChannelScreen { settingsSidebarDrawerButton = element(by.id(this.testID.settingsSidebarDrawerButton)); // convenience props - cameraQuickAction = CameraQuickAction.cameraQuickAction; - cameraQuickActionDisabled = CameraQuickAction.cameraQuickActionDisabled; - imageQuickAction = ImageQuickAction.imageQuickAction; - imageQuickActionDisabled = ImageQuickAction.imageQuickActionDisabled; - fileQuickAction = FileQuickAction.fileQuickAction; - fileQuickActionDisabled = FileQuickAction.fileQuickActionDisabled; - postDraft = PostDraft.postDraft; - postDraftArchived = PostDraft.postDraftArchived; - postDraftReadOnly = PostDraft.postDraftReadOnly; - postInput = PostDraft.postInput; - sendButton = SendButton.sendButton; - sendButtonDisabled = SendButton.sendButtonDisabled; + atInputQuickAction = InputQuickAction.getAtInputQuickAction(this.testID.channelScreenPrefix); + atInputQuickActionDisabled = InputQuickAction.getAtInputQuickActionDisabled(this.testID.channelScreenPrefix); + slashInputQuickAction = InputQuickAction.getSlashInputQuickAction(this.testID.channelScreenPrefix); + slashInputQuickActionDisabled = InputQuickAction.getSlashInputQuickActionDisabled(this.testID.channelScreenPrefix); + fileQuickAction = FileQuickAction.getFileQuickAction(this.testID.channelScreenPrefix); + fileQuickActionDisabled = FileQuickAction.getFileQuickActionDisabled(this.testID.channelScreenPrefix); + imageQuickAction = ImageQuickAction.getImageQuickAction(this.testID.channelScreenPrefix); + imageQuickActionDisabled = ImageQuickAction.getImageQuickActionDisabled(this.testID.channelScreenPrefix); + cameraQuickAction = CameraQuickAction.getCameraQuickAction(this.testID.channelScreenPrefix); + cameraQuickActionDisabled = CameraQuickAction.getCameraQuickActionDisabled(this.testID.channelScreenPrefix); + postDraft = PostDraft.getPostDraft(this.testID.channelScreenPrefix); + postDraftArchived = PostDraft.getPostDraftArchived(this.testID.channelScreenPrefix); + postDraftReadOnly = PostDraft.getPostDraftReadOnly(this.testID.channelScreenPrefix); + postInput = PostDraft.getPostInput(this.testID.channelScreenPrefix); + sendButton = SendButton.getSendButton(this.testID.channelScreenPrefix); + sendButtonDisabled = SendButton.getSendButtonDisabled(this.testID.channelScreenPrefix); getLongPostPostItem = (postId, text) => { return LongPostScreen.getPost(postId, text); } getPostListPostItem = (postId, text) => { - return PostListScreen.getPost(postId, text); + return PostListScreen.getPost(this.testID.channelScreenPrefix, postId, text); } toBeVisible = async () => { @@ -67,7 +73,7 @@ class ChannelScreen { open = async (user) => { // # Open channel screen - await SelectServerScreen.connectToServer(); + await LoginScreen.open(); await LoginScreen.login(user); return this.toBeVisible(); @@ -92,14 +98,21 @@ class ChannelScreen { } openPostOptionsFor = async (postId, text) => { - const post = await this.getPostListPostItem(postId, text); - await expect(post).toBeVisible(); + const {postListPostItem} = await this.getPostListPostItem(postId, text); + await expect(postListPostItem).toBeVisible(); // # Open post options - await post.longPress(); + await postListPostItem.longPress(); await PostOptions.toBeVisible(); } + postMessage = async (message) => { + // # Post message + await this.postInput.tap(); + await this.postInput.typeText(message); + await this.tapSendButton(); + } + tapSendButton = async () => { // # Tap send button await this.sendButton.tap(); diff --git a/detox/e2e/support/ui/screen/create_channel.js b/detox/e2e/support/ui/screen/create_channel.js index 459fed5d0..2b7c8024e 100644 --- a/detox/e2e/support/ui/screen/create_channel.js +++ b/detox/e2e/support/ui/screen/create_channel.js @@ -1,23 +1,24 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {EditChannelInfo} from '@support/ui/component'; + class CreateChannelScreen { testID = { createChannelScreen: 'create_channel.screen', - nameInput: 'edit_channel.name.input', - purposeInput: 'edit_channel.purpose.input', - headerInput: 'edit_channel.header.input', - createButton: 'edit_channel.create.button', + createButton: 'create_channel.create.button', backButton: 'screen.back.button', } createChannelScreen = element(by.id(this.testID.createChannelScreen)); - nameInput = element(by.id(this.testID.nameInput)); - purposeInput = element(by.id(this.testID.purposeInput)); - headerInput = element(by.id(this.testID.headerInput)); createButton = element(by.id(this.testID.createButton)); backButton = element(by.id(this.testID.backButton)); + // convenience props + nameInput = EditChannelInfo.nameInput; + purposeInput = EditChannelInfo.purposeInput; + headerInput = EditChannelInfo.headerInput; + toBeVisible = async () => { await expect(this.createChannelScreen).toBeVisible(); diff --git a/detox/e2e/support/ui/screen/edit_channel.js b/detox/e2e/support/ui/screen/edit_channel.js index 5b7b03b05..f45ad9cbd 100644 --- a/detox/e2e/support/ui/screen/edit_channel.js +++ b/detox/e2e/support/ui/screen/edit_channel.js @@ -1,19 +1,25 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {EditChannelInfo} from '@support/ui/component'; import {ChannelInfoScreen} from '@support/ui/screen'; class EditChannelScreen { testID = { editChannelScreen: 'edit_channel.screen', - editChannelHeaderInput: 'edit_channel.header.input', + saveButton: 'edit_channel.save.button', backButton: 'screen.back.button', } editChannelScreen = element(by.id(this.testID.editChannelScreen)); - editChannelHeaderInput = element(by.id(this.testID.editChannelHeaderInput)); + saveButton = element(by.id(this.testID.saveButton)); backButton = element(by.id(this.testID.backButton)); + // convenience props + nameInput = EditChannelInfo.nameInput; + purposeInput = EditChannelInfo.purposeInput; + headerInput = EditChannelInfo.headerInput; + toBeVisible = async () => { await expect(this.editChannelScreen).toBeVisible(); diff --git a/detox/e2e/support/ui/screen/index.js b/detox/e2e/support/ui/screen/index.js index 6e30b45f3..ba15d56f3 100644 --- a/detox/e2e/support/ui/screen/index.js +++ b/detox/e2e/support/ui/screen/index.js @@ -21,6 +21,7 @@ import RecentMentionsScreen from './recent_mentions'; import SearchResultPostScreen from './search_result_post'; import SearchScreen from './search'; import SelectServerScreen from './select_server'; +import ThreadScreen from './thread'; export { AddReactionScreen, @@ -43,4 +44,5 @@ export { SearchResultPostScreen, SearchScreen, SelectServerScreen, + ThreadScreen, }; diff --git a/detox/e2e/support/ui/screen/login.js b/detox/e2e/support/ui/screen/login.js index 110456fdb..270593aea 100644 --- a/detox/e2e/support/ui/screen/login.js +++ b/detox/e2e/support/ui/screen/login.js @@ -6,17 +6,17 @@ import {SelectServerScreen} from '@support/ui/screen'; class LoginScreen { testID = { loginScreen: 'login.screen', - errorText: 'error.text', - passwordInput: 'password.input', - signinButton: 'signin.button', - usernameInput: 'username.input', + usernameInput: 'login.username.input', + passwordInput: 'login.password.input', + signinButton: 'login.signin.button', + errorText: 'login.error.text', } loginScreen = element(by.id(this.testID.loginScreen)); - errorText = element(by.id(this.testID.errorText)); + usernameInput = element(by.id(this.testID.usernameInput)); passwordInput = element(by.id(this.testID.passwordInput)); signinButton = element(by.id(this.testID.signinButton)); - usernameInput = element(by.id(this.testID.usernameInput)); + errorText = element(by.id(this.testID.errorText)); toBeVisible = async () => { await expect(this.loginScreen).toBeVisible(); diff --git a/detox/e2e/support/ui/screen/long_post.js b/detox/e2e/support/ui/screen/long_post.js index ab61d3e42..7624e9c02 100644 --- a/detox/e2e/support/ui/screen/long_post.js +++ b/detox/e2e/support/ui/screen/long_post.js @@ -9,7 +9,11 @@ class LongPostScreen { } getPost = (postId, text) => { - return Post.getPost(this.testID.longPostScreenPrefix, postId, text); + const {postItem, postItemHeaderReply} = Post.getPost(this.testID.longPostScreenPrefix, postId, text); + return { + longPostItem: postItem, + longPostItemHeaderReply: postItemHeaderReply, + }; } } diff --git a/detox/e2e/support/ui/screen/post_list.js b/detox/e2e/support/ui/screen/post_list.js index 13093e9b7..4a86a5719 100644 --- a/detox/e2e/support/ui/screen/post_list.js +++ b/detox/e2e/support/ui/screen/post_list.js @@ -8,8 +8,12 @@ class PostListScreen { postListScreenPrefix: 'post_list.', } - getPost = (postId, text) => { - return Post.getPost(this.testID.postListScreenPrefix, postId, text); + getPost = (screenPrefix, postId, text) => { + const {postItem, postItemHeaderReply} = Post.getPost(`${screenPrefix}${this.testID.postListScreenPrefix}`, postId, text); + return { + postListPostItem: postItem, + postListPostItemHeaderReply: postItemHeaderReply, + }; } } diff --git a/detox/e2e/support/ui/screen/recent_mentions.js b/detox/e2e/support/ui/screen/recent_mentions.js index 8d800a7c0..c49ab5e7f 100644 --- a/detox/e2e/support/ui/screen/recent_mentions.js +++ b/detox/e2e/support/ui/screen/recent_mentions.js @@ -39,11 +39,11 @@ class RecentMentionsScreen { } openPostOptionsFor = async (postId, text) => { - const post = await this.getSearchResultPostItem(postId, text); - await expect(post).toBeVisible(); + const {searchResultPostItem} = await this.getSearchResultPostItem(postId, text); + await expect(searchResultPostItem).toBeVisible(); // # Open post options - await post.longPress(); + await searchResultPostItem.longPress(); await PostOptions.toBeVisible(); } } diff --git a/detox/e2e/support/ui/screen/search.js b/detox/e2e/support/ui/screen/search.js index f3b86bd81..7ad9f8ae0 100644 --- a/detox/e2e/support/ui/screen/search.js +++ b/detox/e2e/support/ui/screen/search.js @@ -1,23 +1,54 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {ChannelScreen} from '@support/ui/screen'; +import { + RecentItem, + SearchBar, +} from '@support/ui/component'; +import { + ChannelScreen, + SearchResultPostScreen, +} from '@support/ui/screen'; import {isAndroid} from '@support/utils'; class SearchScreen { testID = { + searchScreenPrefix: 'search.', searchScreen: 'search.screen', - searchInput: 'searchInput', - searchFromSection: 'search_from.section', - searchInSection: 'search_in.section', - searchOnSection: 'search_on.section', + searchFromSection: 'search.from_section', + searchInSection: 'search.in_section', + searchOnSection: 'search.on_section', + searchAfterSection: 'search.after_section', + searchBeforeSection: 'search.before_section', } searchScreen = element(by.id(this.testID.searchScreen)); - searchInput = element(by.id(this.testID.searchInput)); searchFromSection = element(by.id(this.testID.searchFromSection)); searchInSection = element(by.id(this.testID.searchInSection)); searchOnSection = element(by.id(this.testID.searchOnSection)); + searchAfterSection = element(by.id(this.testID.searchAfterSection)); + searchBeforeSection = element(by.id(this.testID.searchBeforeSection)); + + searchFromModifier = 'from:'; + searchInModifier = 'in:'; + searchOnModifier = 'on:'; + searchAfterModifier = 'after:'; + searchBeforeModifier = 'before:'; + + // convenience props + searchBar = SearchBar.getSearchBar(this.testID.searchScreenPrefix); + searchInput = SearchBar.getSearchInput(this.testID.searchScreenPrefix); + backButton = SearchBar.getBackButton(this.testID.searchScreenPrefix); + cancelButton = SearchBar.getCancelButton(this.testID.searchScreenPrefix); + clearButton = SearchBar.getClearButton(this.testID.searchScreenPrefix); + + getRecentSearchItem = (searchTerms) => { + return RecentItem.getRecentItem(searchTerms); + } + + getSearchResultPostItem = (postId, text) => { + return SearchResultPostScreen.getPost(postId, text); + } toBeVisible = async () => { await expect(this.searchScreen).toBeVisible(); @@ -32,12 +63,17 @@ class SearchScreen { return this.toBeVisible(); } - cancel = async () => { + back = async () => { if (isAndroid()) { - await device.pressBack(); + await this.backButton.tap(); } else { - await element(by.text('Cancel')).atIndex(0).tap(); + await this.cancelButton.tap(); } + await expect(this.searchScreen).not.toBeVisible(); + } + + clear = async () => { + await this.clearButton.tap(); } } diff --git a/detox/e2e/support/ui/screen/search_result_post.js b/detox/e2e/support/ui/screen/search_result_post.js index 0af5d4f7d..75fb1322d 100644 --- a/detox/e2e/support/ui/screen/search_result_post.js +++ b/detox/e2e/support/ui/screen/search_result_post.js @@ -9,7 +9,11 @@ class SearchResultPostScreen { } getPost = (postId, text) => { - return Post.getPost(this.testID.searchResultPostScreenPrefix, postId, text); + const {postItem, postItemHeaderReply} = Post.getPost(this.testID.searchResultPostScreenPrefix, postId, text); + return { + searchResultPostItem: postItem, + searchResultPostItemHeaderReply: postItemHeaderReply, + }; } } diff --git a/detox/e2e/support/ui/screen/select_server.js b/detox/e2e/support/ui/screen/select_server.js index f2924b2ac..4890a2fc7 100644 --- a/detox/e2e/support/ui/screen/select_server.js +++ b/detox/e2e/support/ui/screen/select_server.js @@ -2,23 +2,25 @@ // See LICENSE.txt for license information. import {serverUrl} from '@support/test_config'; +import {timeouts} from '@support/utils'; class SelectServerScreen { testID = { selectServerScreen: 'select_server.screen', - connectButton: 'connect.button', - errorText: 'error.text', - headerText: 'header.text', - serverUrlInput: 'server_url.input', + headerText: 'select_server.header.text', + serverUrlInput: 'select_server.server_url.input', + connectButton: 'select_server.connect.button', + errorText: 'select_server.error.text', } selectServerScreen = element(by.id(this.testID.selectServerScreen)); + headerText = element(by.id(this.testID.headerText)); serverUrlInput = element(by.id(this.testID.serverUrlInput)); connectButton = element(by.id(this.testID.connectButton)); errorText = element(by.id(this.testID.errorText)); toBeVisible = async () => { - await expect(this.selectServerScreen).toBeVisible(); + await waitFor(this.selectServerScreen).toBeVisible().withTimeout(timeouts.FOUR_SEC); return this.selectServerScreen; } diff --git a/detox/e2e/support/ui/screen/thread.js b/detox/e2e/support/ui/screen/thread.js new file mode 100644 index 000000000..5d41e121e --- /dev/null +++ b/detox/e2e/support/ui/screen/thread.js @@ -0,0 +1,82 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import { + CameraQuickAction, + FileQuickAction, + ImageQuickAction, + InputQuickAction, + PostDraft, + SendButton, +} from '@support/ui/component'; +import { + LongPostScreen, + PostListScreen, +} from '@support/ui/screen'; +import {timeouts, wait} from '@support/utils'; + +class ThreadScreen { + testID = { + threadScreenPrefix: 'thread.', + threadScreen: 'thread.screen', + backButton: 'screen.back.button', + } + + threadScreen = element(by.id(this.testID.threadScreen)); + backButton = element(by.id(this.testID.backButton)); + + // convenience props + atInputQuickAction = InputQuickAction.getAtInputQuickAction(this.testID.threadScreenPrefix); + atInputQuickActionDisabled = InputQuickAction.getAtInputQuickActionDisabled(this.testID.threadScreenPrefix); + slashInputQuickAction = InputQuickAction.getSlashInputQuickAction(this.testID.threadScreenPrefix); + slashInputQuickActionDisabled = InputQuickAction.getSlashInputQuickActionDisabled(this.testID.threadScreenPrefix); + fileQuickAction = FileQuickAction.getFileQuickAction(this.testID.threadScreenPrefix); + fileQuickActionDisabled = FileQuickAction.getFileQuickActionDisabled(this.testID.threadScreenPrefix); + imageQuickAction = ImageQuickAction.getImageQuickAction(this.testID.threadScreenPrefix); + imageQuickActionDisabled = ImageQuickAction.getImageQuickActionDisabled(this.testID.threadScreenPrefix); + cameraQuickAction = CameraQuickAction.getCameraQuickAction(this.testID.threadScreenPrefix); + cameraQuickActionDisabled = CameraQuickAction.getCameraQuickActionDisabled(this.testID.threadScreenPrefix); + postDraft = PostDraft.getPostDraft(this.testID.threadScreenPrefix); + postDraftArchived = PostDraft.getPostDraftArchived(this.testID.threadScreenPrefix); + postDraftReadOnly = PostDraft.getPostDraftReadOnly(this.testID.threadScreenPrefix); + postInput = PostDraft.getPostInput(this.testID.threadScreenPrefix); + sendButton = SendButton.getSendButton(this.testID.threadScreenPrefix); + sendButtonDisabled = SendButton.getSendButtonDisabled(this.testID.threadScreenPrefix); + + getLongPostPostItem = (postId, text) => { + return LongPostScreen.getPost(postId, text); + } + + getPostListPostItem = (postId, text) => { + return PostListScreen.getPost(this.testID.threadScreenPrefix, postId, text); + } + + toBeVisible = async () => { + await wait(timeouts.HALF_SEC); + await expect(this.threadScreen).toBeVisible(); + + return this.threadScreen; + } + + back = async () => { + await this.backButton.tap(); + await expect(this.threadScreen).not.toBeVisible(); + } + + postMessage = async (message) => { + // # Post message + await this.postInput.tap(); + await this.postInput.typeText(message); + await this.tapSendButton(); + } + + tapSendButton = async () => { + // # Tap send button + await this.sendButton.tap(); + await expect(this.sendButton).not.toExist(); + await expect(this.sendButtonDisabled).toBeVisible(); + } +} + +const threadScreen = new ThreadScreen(); +export default threadScreen; diff --git a/detox/e2e/test/autocomplete/edit_channel.e2e.js b/detox/e2e/test/autocomplete/edit_channel.e2e.js index 928cc5630..3cdf51ab0 100644 --- a/detox/e2e/test/autocomplete/edit_channel.e2e.js +++ b/detox/e2e/test/autocomplete/edit_channel.e2e.js @@ -33,7 +33,7 @@ describe('Autocomplete', () => { await EditChannelScreen.open(); // # Activate at_mention autocomplete - await EditChannelScreen.editChannelHeaderInput.typeText('@'); + await EditChannelScreen.headerInput.typeText('@'); // * Expect autocomplete to render await Autocomplete.toBeVisible(); diff --git a/detox/e2e/test/autocomplete/search.e2e.js b/detox/e2e/test/autocomplete/search.e2e.js index 862c0162d..5c109b6f2 100644 --- a/detox/e2e/test/autocomplete/search.e2e.js +++ b/detox/e2e/test/autocomplete/search.e2e.js @@ -8,7 +8,10 @@ // ******************************************************************* import {Autocomplete} from '@support/ui/component'; -import {ChannelScreen, SearchScreen} from '@support/ui/screen'; +import { + ChannelScreen, + SearchScreen, +} from '@support/ui/screen'; import {Setup} from '@support/server_api'; describe('Autocomplete', () => { @@ -27,7 +30,7 @@ describe('Autocomplete', () => { }); beforeEach(async () => { - await SearchScreen.searchInput.atIndex(0).clearText(); + await SearchScreen.searchInput.clearText(); }); afterAll(async () => { diff --git a/detox/e2e/test/channels/create_public_channel.e2e.js b/detox/e2e/test/channels/create_public_channel.e2e.js index cfd2dd20b..43b88a528 100644 --- a/detox/e2e/test/channels/create_public_channel.e2e.js +++ b/detox/e2e/test/channels/create_public_channel.e2e.js @@ -16,6 +16,7 @@ import { MoreChannelsScreen, } from '@support/ui/screen'; import {Setup} from '@support/server_api'; +import {isAndroid} from '@support/utils'; describe('Channels', () => { beforeAll(async () => { @@ -52,7 +53,7 @@ describe('Channels', () => { // # Fill data await nameInput.typeText('a'); - await attemptToTapButton('edit_channel.create.button'); + await attemptToTapCreateButton(); // * Expect to be in the same screen since the channel name must be longer await expect(nameInput).toBeVisible(); @@ -61,7 +62,12 @@ describe('Channels', () => { await purposeInput.typeText('This sentence has'); await purposeInput.tapReturnKey(); await purposeInput.typeText('multiple lines'); - await createChannelScreen.scroll(200, 'down'); + + // # Scroll down if Android + if (isAndroid()) { + await createChannelScreen.scroll(200, 'down'); + } + await expect(headerInput).toBeVisible(); const expectedChannelHeader = 'I 🌮 love 🌮 tacos 🌮'; await headerInput.replaceText(expectedChannelHeader); @@ -86,12 +92,12 @@ describe('Channels', () => { }); }); -async function attemptToTapButton(id) { - if (device.getPlatform() === 'ios') { - const attributes = await element(by.id(id)).getAttributes(); +async function attemptToTapCreateButton() { + if (isAndroid()) { + await CreateChannelScreen.createButton.tap(); + } else { + const attributes = await CreateChannelScreen.createButton.getAttributes(); jestExpect(attributes.visible).toEqual(true); jestExpect(attributes.enabled).toEqual(false); - } else { - await element(by.id(id)).tap(); } } diff --git a/detox/e2e/test/on_boarding/login.e2e.js b/detox/e2e/test/signin_authentication/login.e2e.js similarity index 60% rename from detox/e2e/test/on_boarding/login.e2e.js rename to detox/e2e/test/signin_authentication/login.e2e.js index c9d83b242..c4613aeed 100644 --- a/detox/e2e/test/on_boarding/login.e2e.js +++ b/detox/e2e/test/signin_authentication/login.e2e.js @@ -11,20 +11,11 @@ import {Setup} from '@support/server_api'; import { ChannelScreen, LoginScreen, - SelectServerScreen, } from '@support/ui/screen'; -import {serverUrl} from '@support/test_config'; -import {isAndroid, timeouts, wait} from '@support/utils'; -describe('On boarding', () => { +describe('Login', () => { let user; - const { - connectButton, - errorText, - serverUrlInput, - } = SelectServerScreen; - const { errorText: loginErrorText, usernameInput, @@ -44,73 +35,6 @@ describe('On boarding', () => { await ChannelScreen.logout(); }); - it('should show Select server screen on initial load', async () => { - // * Verify basic elements on Select Server screen - await SelectServerScreen.toBeVisible(); - - await expect(serverUrlInput).toBeVisible(); - await expect(connectButton).toBeVisible(); - }); - - it('MM-T3383 should show error on empty server URL', async () => { - const screen = await SelectServerScreen.toBeVisible(); - - // # Enter an empty server URL - await serverUrlInput.typeText(' '); - - // # Tap anywhere to hide keyboard - await screen.tap({x: 5, y: 10}); - - // * Verify that the error message does not exist - await waitFor(errorText).not.toExist().withTimeout(timeouts.HALF_SEC); - - // # Tap connect button - await connectButton.tap(); - - // # Explicitly wait on Android before verifying error message - if (isAndroid()) { - await wait(timeouts.ONE_SEC); - } - - // * Verify error message - await waitFor(errorText).toBeVisible().withTimeout(timeouts.ONE_SEC); - await expect(errorText).toHaveText('Please enter a valid server URL'); - }); - - it('should show error on invalid server URL', async () => { - const screen = await SelectServerScreen.toBeVisible(); - - // # Enter invalid server URL - await serverUrlInput.clearText(); - await serverUrlInput.typeText(serverUrl.substring(0, serverUrl.length - 1)); - - // # Tap anywhere to hide keyboard - await screen.tap({x: 5, y: 10}); - - // * Verify that the error message does not exist - await waitFor(errorText).not.toExist().withTimeout(timeouts.HALF_SEC); - - // # Tap connect button - await connectButton.tap(); - - // * Verify error message - await waitFor(errorText).toBeVisible().withTimeout(timeouts.ONE_SEC); - await expect(errorText).toHaveText('Cannot connect to the server. Please check your server URL and internet connection.'); - }); - - it('should move to Login screen on valid server URL', async () => { - await SelectServerScreen.toBeVisible(); - - // # Enter valid server URL - await serverUrlInput.replaceText(serverUrl); - - // # Tap connect button - await connectButton.tap(); - - // * Verify that it goes into Login screen - await LoginScreen.toBeVisible(); - }); - it('should match elements on Login screen', async () => { await LoginScreen.open(); diff --git a/detox/e2e/test/signin_authentication/select_server.e2e.js b/detox/e2e/test/signin_authentication/select_server.e2e.js new file mode 100644 index 000000000..0d316ae7a --- /dev/null +++ b/detox/e2e/test/signin_authentication/select_server.e2e.js @@ -0,0 +1,89 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + LoginScreen, + SelectServerScreen, +} from '@support/ui/screen'; +import {serverUrl} from '@support/test_config'; +import {timeouts} from '@support/utils'; + +describe('Select Server', () => { + const { + connectButton, + errorText, + serverUrlInput, + } = SelectServerScreen; + + beforeEach(async () => { + await device.reloadReactNative(); + }); + + it('should show Select server screen on initial load', async () => { + // * Verify basic elements on Select Server screen + await SelectServerScreen.toBeVisible(); + + await expect(serverUrlInput).toBeVisible(); + await expect(connectButton).toBeVisible(); + }); + + it('MM-T3383 should show error on empty server URL', async () => { + const screen = await SelectServerScreen.toBeVisible(); + + // # Enter an empty server URL + await serverUrlInput.typeText(' '); + + // # Tap anywhere to hide keyboard + await screen.tap({x: 5, y: 10}); + + // * Verify that the error message does not exist + await waitFor(errorText).not.toExist().withTimeout(timeouts.HALF_SEC); + + // # Tap connect button + await connectButton.tap(); + + // * Verify error message + await waitFor(errorText).toBeVisible().withTimeout(timeouts.ONE_SEC); + await expect(errorText).toHaveText('Please enter a valid server URL'); + }); + + it('should show error on invalid server URL', async () => { + const screen = await SelectServerScreen.toBeVisible(); + + // # Enter invalid server URL + await serverUrlInput.clearText(); + await serverUrlInput.typeText(serverUrl.substring(0, serverUrl.length - 1)); + + // # Tap anywhere to hide keyboard + await screen.tap({x: 5, y: 10}); + + // * Verify that the error message does not exist + await waitFor(errorText).not.toExist().withTimeout(timeouts.HALF_SEC); + + // # Tap connect button + await connectButton.tap(); + + // * Verify error message + await waitFor(errorText).toBeVisible().withTimeout(timeouts.ONE_SEC); + await expect(errorText).toHaveText('Cannot connect to the server. Please check your server URL and internet connection.'); + }); + + it('should move to Login screen on valid server URL', async () => { + await SelectServerScreen.toBeVisible(); + + // # Enter valid server URL + await serverUrlInput.replaceText(serverUrl); + + // # Tap connect button + await connectButton.tap(); + + // * Verify that it goes into Login screen + await LoginScreen.toBeVisible(); + }); +}); diff --git a/detox/e2e/test/smoke_test/recent_mentions.e2e.js b/detox/e2e/test/smoke_test/recent_mentions.e2e.js index 1d0848ec0..6848f6eef 100644 --- a/detox/e2e/test/smoke_test/recent_mentions.e2e.js +++ b/detox/e2e/test/smoke_test/recent_mentions.e2e.js @@ -55,7 +55,7 @@ describe('Recent Mentions', () => { // * Verify most recent post has the message const lastPost = await Post.apiGetLastPostInChannel(testChannel.id); - const postListPostItem = await ChannelScreen.getPostListPostItem(lastPost.post.id, testMessage); + const {postListPostItem} = await ChannelScreen.getPostListPostItem(lastPost.post.id, testMessage); await expect(postListPostItem).toBeVisible(); // # Open recent mentions screen @@ -64,7 +64,7 @@ describe('Recent Mentions', () => { // * Verify most recent search result post has the at-mention message const lastSearchResultPost = await Post.apiGetLastPostInChannel(testChannel.id); - const searchResultPostItem = await RecentMentionsScreen.getSearchResultPostItem(lastSearchResultPost.post.id, testMessage); + const {searchResultPostItem} = await RecentMentionsScreen.getSearchResultPostItem(lastSearchResultPost.post.id, testMessage); await expect(searchResultPostItem).toBeVisible(); // * Verify recent mentions post item does not have option to add a reaction diff --git a/detox/e2e/test/smoke_test/search.e2e.js b/detox/e2e/test/smoke_test/search.e2e.js new file mode 100644 index 000000000..2d3f2782f --- /dev/null +++ b/detox/e2e/test/smoke_test/search.e2e.js @@ -0,0 +1,104 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import {Autocomplete} from '@support/ui/component'; +import { + ChannelScreen, + SearchScreen, + ThreadScreen, +} from '@support/ui/screen'; +import { + Channel, + Post, + Setup, +} from '@support/server_api'; + +describe('Search', () => { + let testUser; + let testChannel; + + const {atMentionSuggestionList} = Autocomplete; + + beforeAll(async () => { + const {team, user} = await Setup.apiInit(); + testUser = user; + + const {channel} = await Channel.apiGetChannelByName(team.name, 'town-square'); + testChannel = channel; + + // # Open channel screen + await ChannelScreen.open(user); + }); + + afterAll(async () => { + await ChannelScreen.logout(); + }); + + it('MM-T3236 search on sender, select from autocomplete, reply from search results', async () => { + const testMessage = Date.now().toString(); + const { + searchFromModifier, + searchFromSection, + searchInput, + } = SearchScreen; + + // # Post a message + await ChannelScreen.postMessage(testMessage); + + // # Open search screen + await SearchScreen.open(); + + // # Tap "from:" modifier + await searchInput.clearText(); + await searchFromSection.tap(); + + // # Type beginning of search term + const searchTerm = testUser.first_name; + await searchInput.typeText(searchTerm); + + // # Select user from autocomplete + await expect(atMentionSuggestionList).toExist(); + const userAtMentionAutocomplete = await Autocomplete.getAtMentionItem(testUser.id); + await userAtMentionAutocomplete.tap(); + + // # Search user + await searchInput.tapReturnKey(); + await expect(atMentionSuggestionList).not.toExist(); + + // * Verify recent search item is displayed + const searchTerms = `${searchFromModifier} ${testUser.username}`; + const recentSearchItem = await SearchScreen.getRecentSearchItem(searchTerms); + await expect(recentSearchItem).toBeVisible(); + + // * Verify search result post has the message + const lastPost = await Post.apiGetLastPostInChannel(testChannel.id); + const { + searchResultPostItem, + searchResultPostItemHeaderReply, + } = await SearchScreen.getSearchResultPostItem(lastPost.post.id, testMessage); + await expect(searchResultPostItem).toBeVisible(); + + // # Open reply thread from search result post item + const replyTestMessage = `reply-${testMessage}`; + searchResultPostItemHeaderReply.tap(); + + // # Post a reply message + await ThreadScreen.toBeVisible(); + await ThreadScreen.postMessage(replyTestMessage); + + // * Verify most recent post has the reply message + const replyPost = await Post.apiGetLastPostInChannel(testChannel.id); + const {postListPostItem} = await ThreadScreen.getPostListPostItem(replyPost.post.id, replyTestMessage); + await expect(postListPostItem).toBeVisible(); + + // # Go back to channel + await ThreadScreen.back(); + await SearchScreen.back(); + }); +}); diff --git a/share_extension/screens/channel_list.tsx b/share_extension/screens/channel_list.tsx index 2034315cf..487a31c60 100644 --- a/share_extension/screens/channel_list.tsx +++ b/share_extension/screens/channel_list.tsx @@ -146,9 +146,13 @@ const ChannelList = ({intl}: ChannnelListProps) => { } return ( - +