From 2e2f49f8c3fc9c4f630ffc916f6d55b62cfc4345 Mon Sep 17 00:00:00 2001 From: Joseph Baylon Date: Tue, 12 Apr 2022 11:50:05 -0700 Subject: [PATCH] Detox/E2E: Create Channel and Channel Post List e2e in Gekidou --- .gitignore | 1 - .../post_draft/send_action/index.tsx | 2 +- app/components/post_list/index.tsx | 2 +- app/components/section_item/index.tsx | 1 + .../add_team/add_team_slide_up.tsx | 1 + app/init/launch.ts | 9 +- app/screens/bottom_sheet/index.tsx | 4 +- .../browse_channels/dropdown_slideup.tsx | 1 + app/screens/channel/channel.tsx | 1 + .../channel_info_form.tsx | 13 +- .../create_or_edit_channel.tsx | 4 +- .../copy_permalink_option.tsx | 2 +- .../post_options/options/copy_text_option.tsx | 2 +- .../options/delete_post_option.tsx | 2 +- .../post_options/options/edit_option.tsx | 2 +- .../post_options/options/follow_option.tsx | 2 +- .../options/mark_unread_option.tsx | 2 +- .../options/pin_channel_option.tsx | 2 +- .../post_options/options/reply_option.tsx | 2 +- .../post_options/options/save_option.tsx | 2 +- app/screens/post_options/post_options.tsx | 1 + .../reaction_bar/pick_reaction/index.tsx | 1 + .../reaction_bar/reaction/index.tsx | 4 +- .../reaction_bar/reaction_bar.tsx | 1 + app/screens/reactions/reactions.tsx | 1 + app/screens/thread/thread.tsx | 1 + babel.config.js | 8 + detox/e2e/support/ui/component/alert.ts | 2 + .../ui/component/camera_quick_action.ts | 20 +++ .../support/ui/component/file_quick_action.ts | 20 +++ .../ui/component/image_quick_action.ts | 20 +++ detox/e2e/support/ui/component/index.ts | 18 +++ .../ui/component/input_quick_action.ts | 30 ++++ detox/e2e/support/ui/component/post.ts | 109 ++++++++++++++ detox/e2e/support/ui/component/post_draft.ts | 30 ++++ detox/e2e/support/ui/component/post_list.ts | 83 +++++++++++ .../support/ui/component/profile_picture.ts | 21 +++ detox/e2e/support/ui/component/send_button.ts | 20 +++ detox/e2e/support/ui/screen/channel.ts | 96 +++++++++++- detox/e2e/support/ui/screen/channel_list.ts | 2 + .../ui/screen/create_or_edit_channel.ts | 80 ++++++++++ detox/e2e/support/ui/screen/home.ts | 2 +- detox/e2e/support/ui/screen/index.ts | 6 + detox/e2e/support/ui/screen/post_options.ts | 79 ++++++++++ detox/e2e/support/ui/screen/thread.ts | 31 ++++ .../e2e/test/channels/browse_channels.e2e.ts | 6 +- detox/e2e/test/channels/channel_list.e2e.ts | 13 +- .../test/channels/channel_post_list.e2e.ts | 98 ++++++++++++ .../channels/create_and_edit_header.e2e.ts | 139 ++++++++++++++++++ .../channels/create_direct_message.e2e.ts | 14 +- detox/e2e/test/smoke_test/channels.e2e.ts | 130 ++++++++++++++++ package-lock.json | 43 ++++++ package.json | 2 + env.d.ts => types/env.d.ts | 1 + 54 files changed, 1154 insertions(+), 35 deletions(-) create mode 100644 detox/e2e/support/ui/component/camera_quick_action.ts create mode 100644 detox/e2e/support/ui/component/file_quick_action.ts create mode 100644 detox/e2e/support/ui/component/image_quick_action.ts create mode 100644 detox/e2e/support/ui/component/input_quick_action.ts create mode 100644 detox/e2e/support/ui/component/post.ts create mode 100644 detox/e2e/support/ui/component/post_draft.ts create mode 100644 detox/e2e/support/ui/component/post_list.ts create mode 100644 detox/e2e/support/ui/component/profile_picture.ts create mode 100644 detox/e2e/support/ui/component/send_button.ts create mode 100644 detox/e2e/support/ui/screen/create_or_edit_channel.ts create mode 100644 detox/e2e/support/ui/screen/post_options.ts create mode 100644 detox/e2e/support/ui/screen/thread.ts create mode 100644 detox/e2e/test/channels/channel_post_list.e2e.ts create mode 100644 detox/e2e/test/channels/create_and_edit_header.e2e.ts create mode 100644 detox/e2e/test/smoke_test/channels.e2e.ts rename env.d.ts => types/env.d.ts (85%) diff --git a/.gitignore b/.gitignore index e2711cebf..a4c216fd0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ server.PID mattermost.keystore tmp/ .env -env.d.ts */**/compass-icons.ttf diff --git a/app/components/post_draft/send_action/index.tsx b/app/components/post_draft/send_action/index.tsx index 858383ca8..5795370e6 100644 --- a/app/components/post_draft/send_action/index.tsx +++ b/app/components/post_draft/send_action/index.tsx @@ -41,7 +41,7 @@ function SendButton({ sendMessage, }: Props) { const theme = useTheme(); - const sendButtonTestID = `${testID}.send.button`; + const sendButtonTestID = disabled ? `${testID}.send.button.disabled` : `${testID}.send.button`; const style = getStyleSheet(theme); const viewStyle = useMemo(() => { diff --git a/app/components/post_list/index.tsx b/app/components/post_list/index.tsx index af7cfca39..a4fc270af 100644 --- a/app/components/post_list/index.tsx +++ b/app/components/post_list/index.tsx @@ -363,7 +363,7 @@ const PostList = ({ scrollEventThrottle={60} style={styles.flex} viewabilityConfig={VIEWABILITY_CONFIG} - testID={testID} + testID={`${testID}.flat_list`} /> {showMoreMessages && diff --git a/app/components/section_item/index.tsx b/app/components/section_item/index.tsx index 6eb3e2c3e..3c448e1b8 100644 --- a/app/components/section_item/index.tsx +++ b/app/components/section_item/index.tsx @@ -93,6 +93,7 @@ const SectionItem = ({testID = 'sectionItem', action, actionType, actionValue, l ); } else if (actionType === ActionTypes.ARROW) { diff --git a/app/components/team_sidebar/add_team/add_team_slide_up.tsx b/app/components/team_sidebar/add_team/add_team_slide_up.tsx index fb6f473a0..cdbacf167 100644 --- a/app/components/team_sidebar/add_team/add_team_slide_up.tsx +++ b/app/components/team_sidebar/add_team/add_team_slide_up.tsx @@ -32,6 +32,7 @@ export default function AddTeamSlideUp({otherTeams, canCreateTeams, showTitle = onPress={onPressCreate} showButton={canCreateTeams} showTitle={showTitle} + testID='add_team_slide_up' title={intl.formatMessage({id: 'mobile.add_team.join_team', defaultMessage: 'Join Another Team'})} > diff --git a/app/init/launch.ts b/app/init/launch.ts index c2af77a97..aa8d49779 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -1,8 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {RUNNING_E2E} from '@env'; import Emm from '@mattermost/react-native-emm'; -import {Alert, Linking, Platform} from 'react-native'; +import {Alert, Linking, LogBox, Platform} from 'react-native'; import {Notifications} from 'react-native-notifications'; import {appEntry, pushNotificationEntry, upgradeEntry} from '@actions/remote/entry'; @@ -219,3 +220,9 @@ export const getLaunchPropsFromNotification = async (notification: NotificationW return launchProps; }; + +// Ignore all notifications if running e2e +const isRunningE2e = RUNNING_E2E === 'true'; +// eslint-disable-next-line no-console +console.log(`RUNNING_E2E: ${RUNNING_E2E}, isRunningE2e: ${isRunningE2e}`); +LogBox.ignoreAllLogs(isRunningE2e); diff --git a/app/screens/bottom_sheet/index.tsx b/app/screens/bottom_sheet/index.tsx index 3a60edb1e..4a7f61dd3 100644 --- a/app/screens/bottom_sheet/index.tsx +++ b/app/screens/bottom_sheet/index.tsx @@ -23,9 +23,10 @@ type SlideUpPanelProps = { initialSnapIndex?: number; renderContent: () => ReactNode; snapPoints?: Array; + testID?: string; } -const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderContent, snapPoints = ['90%', '50%', 50]}: SlideUpPanelProps) => { +const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderContent, snapPoints = ['90%', '50%', 50], testID}: SlideUpPanelProps) => { const sheetRef = useRef(null); const dimensions = useWindowDimensions(); const isTablet = useIsTablet(); @@ -121,6 +122,7 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo width: isTablet ? '100%' : Math.min(dimensions.width, 450), alignSelf: 'center', }} + testID={`${testID}.screen`} > {renderContent()} diff --git a/app/screens/browse_channels/dropdown_slideup.tsx b/app/screens/browse_channels/dropdown_slideup.tsx index b8eac1fcd..5f68e1246 100644 --- a/app/screens/browse_channels/dropdown_slideup.tsx +++ b/app/screens/browse_channels/dropdown_slideup.tsx @@ -66,6 +66,7 @@ export default function DropdownSlideup({ } diff --git a/app/screens/create_or_edit_channel/channel_info_form.tsx b/app/screens/create_or_edit_channel/channel_info_form.tsx index af5a1d4f7..42ae9d40f 100644 --- a/app/screens/create_or_edit_channel/channel_info_form.tsx +++ b/app/screens/create_or_edit_channel/channel_info_form.tsx @@ -224,9 +224,10 @@ export default function ChannelInfoForm({ {showSelector && ( )} @@ -315,7 +317,7 @@ export default function ChannelInfoForm({ returnKeyType='next' showErrorIcon={false} spellCheck={false} - testID='edit_channel_info.header.input' + testID='channel_info_form.header.input' value={header} onLayout={onHeaderLayout} ref={headerInput} @@ -326,6 +328,7 @@ export default function ChannelInfoForm({ style={styles.helpText} id='channel_modal.headerHelp' defaultMessage={'Specify text to appear in the channel header beside the channel name. For example, include frequently used links by typing link text [Link Title](http://example.com).'} + testID='channel_info_form.header.description' /> diff --git a/app/screens/create_or_edit_channel/create_or_edit_channel.tsx b/app/screens/create_or_edit_channel/create_or_edit_channel.tsx index 90b55e1ae..fad0b52ec 100644 --- a/app/screens/create_or_edit_channel/create_or_edit_channel.tsx +++ b/app/screens/create_or_edit_channel/create_or_edit_channel.tsx @@ -58,7 +58,7 @@ const isDirect = (channel?: ChannelModel): boolean => { }; const makeCloseButton = (icon: ImageResource) => { - return buildNavigationButton(CLOSE_BUTTON_ID, 'close.more_direct_messages.button', icon); + return buildNavigationButton(CLOSE_BUTTON_ID, 'close.create_or_edit_channel.button', icon); }; const CreateOrEditChannel = ({ @@ -111,7 +111,7 @@ const CreateOrEditChannel = ({ const rightButton = useMemo(() => { const base = buildNavigationButton( editing ? EDIT_BUTTON_ID : CREATE_BUTTON_ID, - 'edit_channel.save.button', + editing ? 'create_or_edit_channel.save.button' : 'create_or_edit_channel.create.button', undefined, editing ? formatMessage({id: 'mobile.edit_channel', defaultMessage: 'Save'}) : formatMessage({id: 'mobile.create_channel', defaultMessage: 'Create'}), ); diff --git a/app/screens/post_options/options/copy_permalink_option/copy_permalink_option.tsx b/app/screens/post_options/options/copy_permalink_option/copy_permalink_option.tsx index 47d9a3142..8528709df 100644 --- a/app/screens/post_options/options/copy_permalink_option/copy_permalink_option.tsx +++ b/app/screens/post_options/options/copy_permalink_option/copy_permalink_option.tsx @@ -32,7 +32,7 @@ const CopyPermalinkOption = ({teamName, post}: Props) => { defaultMessage='Copy Link' onPress={handleCopyLink} iconName='link-variant' - testID='post.options.copy.permalink' + testID='post_options.copy.permalink.option' /> ); }; diff --git a/app/screens/post_options/options/copy_text_option.tsx b/app/screens/post_options/options/copy_text_option.tsx index 81acc2bc2..ade8fb847 100644 --- a/app/screens/post_options/options/copy_text_option.tsx +++ b/app/screens/post_options/options/copy_text_option.tsx @@ -25,7 +25,7 @@ const CopyTextOption = ({postMessage}: Props) => { defaultMessage='Copy Text' iconName='content-copy' onPress={handleCopyText} - testID='post.options.copy.text' + testID='post_options.copy.text.option' /> ); }; diff --git a/app/screens/post_options/options/delete_post_option.tsx b/app/screens/post_options/options/delete_post_option.tsx index 36b715426..43d2c8c81 100644 --- a/app/screens/post_options/options/delete_post_option.tsx +++ b/app/screens/post_options/options/delete_post_option.tsx @@ -50,7 +50,7 @@ const DeletePostOption = ({combinedPost, post}: Props) => { defaultMessage='Delete' iconName='trash-can-outline' onPress={onPress} - testID='post.options.delete.post' + testID='post_options.delete.post.option' isDestructive={true} /> ); diff --git a/app/screens/post_options/options/edit_option.tsx b/app/screens/post_options/options/edit_option.tsx index a87aea1c9..a442cdfcd 100644 --- a/app/screens/post_options/options/edit_option.tsx +++ b/app/screens/post_options/options/edit_option.tsx @@ -48,7 +48,7 @@ const EditOption = ({post, canDelete}: Props) => { defaultMessage='Edit' onPress={onPress} iconName='pencil-outline' - testID='post.options.edit' + testID='post_options.edit.post.option' /> ); }; diff --git a/app/screens/post_options/options/follow_option.tsx b/app/screens/post_options/options/follow_option.tsx index 135da6af0..024f20226 100644 --- a/app/screens/post_options/options/follow_option.tsx +++ b/app/screens/post_options/options/follow_option.tsx @@ -48,7 +48,7 @@ const FollowThreadOption = ({thread}: FollowThreadOptionProps) => { diff --git a/app/screens/post_options/options/mark_unread_option.tsx b/app/screens/post_options/options/mark_unread_option.tsx index 14805ead1..b7e2e12a4 100644 --- a/app/screens/post_options/options/mark_unread_option.tsx +++ b/app/screens/post_options/options/mark_unread_option.tsx @@ -29,7 +29,7 @@ const MarkAsUnreadOption = ({postId}: Props) => { defaultMessage='Mark as Unread' iconName='mark-as-unread' onPress={onPress} - testID='post.options.mark.unread' + testID='post_options.mark.unread.option' /> ); }; diff --git a/app/screens/post_options/options/pin_channel_option.tsx b/app/screens/post_options/options/pin_channel_option.tsx index 82d27f81b..add96a169 100644 --- a/app/screens/post_options/options/pin_channel_option.tsx +++ b/app/screens/post_options/options/pin_channel_option.tsx @@ -44,7 +44,7 @@ const PinChannelOption = ({isPostPinned, postId}: PinChannelProps) => { defaultMessage={defaultMessage} iconName='pin-outline' onPress={onPress} - testID={`post.options.${key}.channel`} + testID={`post.options.${key}.channel.option`} /> ); }; diff --git a/app/screens/post_options/options/reply_option.tsx b/app/screens/post_options/options/reply_option.tsx index f0dfeac33..ce89af3c7 100644 --- a/app/screens/post_options/options/reply_option.tsx +++ b/app/screens/post_options/options/reply_option.tsx @@ -31,7 +31,7 @@ const ReplyOption = ({post}: Props) => { defaultMessage='Reply' iconName='reply-outline' onPress={handleReply} - testID='post.options.reply' + testID='post_options.reply.post.option' /> ); }; diff --git a/app/screens/post_options/options/save_option.tsx b/app/screens/post_options/options/save_option.tsx index 13adb93b9..21657e242 100644 --- a/app/screens/post_options/options/save_option.tsx +++ b/app/screens/post_options/options/save_option.tsx @@ -34,7 +34,7 @@ const SaveOption = ({isSaved, postId}: CopyTextProps) => { defaultMessage={defaultMessage} iconName='bookmark-outline' onPress={onHandlePress} - testID={id} + testID={`post.options.${defaultMessage.toLocaleLowerCase()}.channel.option`} /> ); }; diff --git a/app/screens/post_options/post_options.tsx b/app/screens/post_options/post_options.tsx index caa8ec762..48c5749ac 100644 --- a/app/screens/post_options/post_options.tsx +++ b/app/screens/post_options/post_options.tsx @@ -140,6 +140,7 @@ const PostOptions = ({ componentId={Screens.POST_OPTIONS} initialSnapIndex={0} snapPoints={[((snapPoints + additionalSnapPoints) * ITEM_HEIGHT), 10]} + testID='post_options' /> ); }; diff --git a/app/screens/post_options/reaction_bar/pick_reaction/index.tsx b/app/screens/post_options/reaction_bar/pick_reaction/index.tsx index 96f81fab1..ebdc11dea 100644 --- a/app/screens/post_options/reaction_bar/pick_reaction/index.tsx +++ b/app/screens/post_options/reaction_bar/pick_reaction/index.tsx @@ -37,6 +37,7 @@ const PickReaction = ({openEmojiPicker, width, height}: PickReactionProps) => { style={[styles.container, { width, height, }]} + testID='post_options.reaction_bar.pick_reaction' > { +const Reaction = ({onPressReaction, emoji, iconSize, containerSize, testID}: ReactionProps) => { const theme = useTheme(); const styles = getStyleSheet(theme); const handleReactionPressed = useCallback(() => { @@ -53,6 +54,7 @@ const Reaction = ({onPressReaction, emoji, iconSize, containerSize}: ReactionPro key={emoji} onPress={handleReactionPressed} style={highlightedStyle} + testID={`${testID}.${emoji}`} > { emoji={emoji} iconSize={iconSize} containerSize={containerSize} + testID='post_options.reaction_bar.reaction' /> ); }) diff --git a/app/screens/reactions/reactions.tsx b/app/screens/reactions/reactions.tsx index fd8685416..899e75afd 100644 --- a/app/screens/reactions/reactions.tsx +++ b/app/screens/reactions/reactions.tsx @@ -80,6 +80,7 @@ const Reactions = ({initialEmoji, reactions}: Props) => { componentId={Screens.REACTIONS} initialSnapIndex={1} snapPoints={['90%', '50%', 10]} + testID='reactions' /> ); }; diff --git a/app/screens/thread/thread.tsx b/app/screens/thread/thread.tsx index 0ba4bdd51..a8bf6ca08 100644 --- a/app/screens/thread/thread.tsx +++ b/app/screens/thread/thread.tsx @@ -53,6 +53,7 @@ const Thread = ({rootPost}: ThreadProps) => { accessoriesContainerID={THREAD_ACCESSORIES_CONTAINER_NATIVE_ID} rootId={rootPost!.id} keyboardTracker={postDraftRef} + testID='thread.post_draft' /> } diff --git a/babel.config.js b/babel.config.js index d74953cda..daeb43aea 100644 --- a/babel.config.js +++ b/babel.config.js @@ -44,6 +44,14 @@ module.exports = { '@websocket': './app/client/websocket', }, }], + ['module:react-native-dotenv', { + moduleName: '@env', + path: '.env', + blacklist: null, + whitelist: null, + safe: false, + allowUndefined: true, + }], 'react-native-reanimated/plugin', ], exclude: ['**/*.png', '**/*.jpg', '**/*.gif'], diff --git a/detox/e2e/support/ui/component/alert.ts b/detox/e2e/support/ui/component/alert.ts index 868eece19..5015a5516 100644 --- a/detox/e2e/support/ui/component/alert.ts +++ b/detox/e2e/support/ui/component/alert.ts @@ -5,6 +5,7 @@ import {isAndroid} from '@support/utils'; class Alert { // alert titles + deletePostTitle = isAndroid() ? element(by.text('Delete Post')) : element(by.label('Delete Post')).atIndex(0); logoutTitle = (serverDisplayName: string) => { const title = `Are you sure you want to log out of ${serverDisplayName}?`; @@ -18,6 +19,7 @@ class Alert { // alert buttons cancelButton = isAndroid() ? element(by.text('CANCEL')) : element(by.label('Cancel')).atIndex(1); + deleteButton = isAndroid() ? element(by.text('DELETE')) : element(by.label('Delete')).atIndex(0); logoutButton = isAndroid() ? element(by.text('LOG OUT')) : element(by.label('Log out')).atIndex(1); removeButton = isAndroid() ? element(by.text('REMOVE')) : element(by.label('Remove')).atIndex(1); } diff --git a/detox/e2e/support/ui/component/camera_quick_action.ts b/detox/e2e/support/ui/component/camera_quick_action.ts new file mode 100644 index 000000000..19024cf7f --- /dev/null +++ b/detox/e2e/support/ui/component/camera_quick_action.ts @@ -0,0 +1,20 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class CameraQuickAction { + testID = { + cameraActionSuffix: 'post_draft.quick_actions.camera_action', + cameraActionDisabledSuffix: 'post_draft.quick_actions.camera_action.disabled', + }; + + getCameraQuickAction = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.cameraActionSuffix}`)); + }; + + getCameraQuickActionDisabled = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.cameraActionDisabledSuffix}`)); + }; +} + +const cameraQuickAction = new CameraQuickAction(); +export default cameraQuickAction; diff --git a/detox/e2e/support/ui/component/file_quick_action.ts b/detox/e2e/support/ui/component/file_quick_action.ts new file mode 100644 index 000000000..2421fec11 --- /dev/null +++ b/detox/e2e/support/ui/component/file_quick_action.ts @@ -0,0 +1,20 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class FileQuickAction { + testID = { + fileActionSuffix: 'post_draft.quick_actions.file_action', + fileActionDisabledSuffix: 'post_draft.quick_actions.file_action.disabled', + }; + + getFileQuickAction = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.fileActionSuffix}`)); + }; + + getFileQuickActionDisabled = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.fileActionDisabledSuffix}`)); + }; +} + +const fileQuickAction = new FileQuickAction(); +export default fileQuickAction; diff --git a/detox/e2e/support/ui/component/image_quick_action.ts b/detox/e2e/support/ui/component/image_quick_action.ts new file mode 100644 index 000000000..d5fd1a0e1 --- /dev/null +++ b/detox/e2e/support/ui/component/image_quick_action.ts @@ -0,0 +1,20 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class ImageQuickAction { + testID = { + imageActionSuffix: 'post_draft.quick_actions.image_action', + imageActionDisabledSuffix: 'post_draft.quick_actions.image_action.disabled', + }; + + getImageQuickAction = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.imageActionSuffix}`)); + }; + + getImageQuickActionDisabled = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.imageActionDisabledSuffix}`)); + }; +} + +const imageQuickAction = new ImageQuickAction(); +export default imageQuickAction; diff --git a/detox/e2e/support/ui/component/index.ts b/detox/e2e/support/ui/component/index.ts index a5eace445..af4d253af 100644 --- a/detox/e2e/support/ui/component/index.ts +++ b/detox/e2e/support/ui/component/index.ts @@ -2,11 +2,29 @@ // See LICENSE.txt for license information. import Alert from './alert'; +import CameraQuickAction from './camera_quick_action'; +import FileQuickAction from './file_quick_action'; +import ImageQuickAction from './image_quick_action'; +import InputQuickAction from './input_quick_action'; import NavigationHeader from './navigation_header'; import PlusMenu from './plus_menu'; +import Post from './post'; +import PostDraft from './post_draft'; +import PostList from './post_list'; +import ProfilePicture from './profile_picture'; +import SendButton from './send_button'; export { Alert, + CameraQuickAction, + FileQuickAction, + ImageQuickAction, + InputQuickAction, NavigationHeader, PlusMenu, + Post, + PostDraft, + PostList, + ProfilePicture, + SendButton, }; diff --git a/detox/e2e/support/ui/component/input_quick_action.ts b/detox/e2e/support/ui/component/input_quick_action.ts new file mode 100644 index 000000000..452820bb4 --- /dev/null +++ b/detox/e2e/support/ui/component/input_quick_action.ts @@ -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: string) => { + return element(by.id(`${screenPrefix}${this.testID.atInputActionSuffix}`)); + }; + + getAtInputQuickActionDisabled = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.atInputActionDisabledSuffix}`)); + }; + + getSlashInputQuickAction = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.slashInputActionSuffix}`)); + }; + + getSlashInputQuickActionDisabled = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.slashInputActionDisabledSuffix}`)); + }; +} + +const inputQuickAction = new InputQuickAction(); +export default inputQuickAction; diff --git a/detox/e2e/support/ui/component/post.ts b/detox/e2e/support/ui/component/post.ts new file mode 100644 index 000000000..375fd3cfb --- /dev/null +++ b/detox/e2e/support/ui/component/post.ts @@ -0,0 +1,109 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import ProfilePicture from './profile_picture'; + +class Post { + testID = { + postProfilePicturePrefix: 'post_profile_picture.profile_picture.', + blockQuote: 'markdown_block_quote', + emoji: 'markdown_emoji', + image: 'markdown_image', + message: 'markdown_text', + postHeaderCommentedOn: 'post_header.commented_on', + postHeaderDateTime: 'post_header.date_time', + postHeaderDisplayName: 'post_header.display_name', + postHeaderBotTag: 'post_header.bot_tag', + postHeaderGuestTag: 'post_header.guest_tag', + postHeaderReply: 'post_header.reply', + postHeaderReplyCount: 'post_header.reply.count', + postPreHeaderText: 'post_pre_header.text', + showLessButton: 'show_more.button.chevron-up', + showMoreButton: 'show_more.button.chevron-down', + table: 'markdown_table', + tableExpandButton: 'markdown_table.expand.button', + thematicBreak: 'markdown_thematic_break', + }; + + getPost = (postItemSourceTestID: string, postId: string, postMessage: string, postProfileOptions: any = {}) => { + const postItemMatcher = this.getPostItemMatcher(postItemSourceTestID, postId, postMessage); + const postItemBlockQuoteMatcher = by.id(this.testID.blockQuote).withAncestor(postItemMatcher); + const postItemEmojiMatcher = by.id(this.testID.emoji).withAncestor(postItemMatcher); + const postItemImageMatcher = by.id(this.testID.image).withAncestor(postItemMatcher); + const postItemMessageMatcher = by.id(this.testID.message).withAncestor(postItemMatcher); + const postItemPreHeaderTextMatch = by.id(this.testID.postPreHeaderText).withAncestor(postItemMatcher); + const postItemShowLessButtonMatcher = by.id(this.testID.showLessButton).withAncestor(postItemMatcher); + const postItemShowMoreButtonMatcher = by.id(this.testID.showMoreButton).withAncestor(postItemMatcher); + const postItemTableMatcher = by.id(this.testID.table).withAncestor(postItemMatcher); + const postItemTableExpandButtonMatcher = by.id(this.testID.tableExpandButton).withAncestor(postItemMatcher); + const postItemThematicBreakMatcher = by.id(this.testID.thematicBreak).withAncestor(postItemMatcher); + + return { + postItem: element(postItemMatcher), + postItemBlockQuote: element(postItemBlockQuoteMatcher), + postItemEmoji: element(postItemEmojiMatcher), + postItemImage: element(postItemImageMatcher), + postItemMessage: element(postItemMessageMatcher), + postItemPreHeaderText: element(postItemPreHeaderTextMatch), + postItemShowLessButton: element(postItemShowLessButtonMatcher), + postItemShowMoreButton: element(postItemShowMoreButtonMatcher), + postItemTable: element(postItemTableMatcher), + postItemTableExpandButton: element(postItemTableExpandButtonMatcher), + postItemThematicBreak: element(postItemThematicBreakMatcher), + ...this.getPostHeader(postItemMatcher), + ...this.getPostProfilePicture(postItemMatcher, postProfileOptions), + }; + }; + + getPostHeader = (postItemMatcher: Detox.NativeMatcher) => { + const postItemHeaderCommentedOnMatcher = by.id(this.testID.postHeaderCommentedOn).withAncestor(postItemMatcher); + const postItemHeaderDateTimeMatcher = by.id(this.testID.postHeaderDateTime).withAncestor(postItemMatcher); + const postItemHeaderDisplayNameMatcher = by.id(this.testID.postHeaderDisplayName).withAncestor(postItemMatcher); + const postItemHeaderBotTagMatcher = by.id(this.testID.postHeaderBotTag).withAncestor(postItemMatcher); + const postItemHeaderGuestTagMatcher = by.id(this.testID.postHeaderGuestTag).withAncestor(postItemMatcher); + const postItemHeaderReplyMatcher = by.id(this.testID.postHeaderReply).withAncestor(postItemMatcher); + const postItemHeaderReplyCountMatcher = by.id(this.testID.postHeaderReplyCount).withAncestor(postItemMatcher); + + return { + postItemHeaderCommentedOn: element(postItemHeaderCommentedOnMatcher), + postItemHeaderDateTime: element(postItemHeaderDateTimeMatcher), + postItemHeaderDisplayName: element(postItemHeaderDisplayNameMatcher), + postItemHeaderBotTag: element(postItemHeaderBotTagMatcher), + postItemHeaderGuestTag: element(postItemHeaderGuestTagMatcher), + postItemHeaderReply: element(postItemHeaderReplyMatcher), + postItemHeaderReplyCount: element(postItemHeaderReplyCountMatcher), + }; + }; + + getPostItemMatcher = (postItemSourceTestID: string, postId: string, postMessage: string) => { + const postTestID = `${postItemSourceTestID}.${postId}`; + const baseMatcher = by.id(postTestID); + return postMessage ? baseMatcher.withDescendant(by.text(postMessage)) : baseMatcher; + }; + + getPostMessage = (postItemSourceTestID: string) => { + return element(by.id(this.testID.message).withAncestor(by.id(postItemSourceTestID))); + }; + + getPostProfilePicture = (postItemMatcher: Detox.NativeMatcher, postProfileOptions = {userId: null, userStatus: 'online'}) => { + if (Object.keys(postProfileOptions).length === 0 || !postProfileOptions.userId) { + return { + postItemProfilePicture: null, + postItemProfilePictureUserStatus: null, + }; + } + + const profilePictureItemMatcher = ProfilePicture.getProfilePictureItemMatcher(this.testID.postProfilePicturePrefix, postProfileOptions.userId); + const profilePictureItemUserStatusMatcher = ProfilePicture.getProfilePictureItemUserStatusMatcher(profilePictureItemMatcher, postProfileOptions.userStatus); + const postItemProfilePictureMatcher = profilePictureItemMatcher.withAncestor(postItemMatcher); + const postItemProfilePictureUserStatusMatcher = profilePictureItemUserStatusMatcher.withAncestor(postItemMatcher); + + return { + postItemProfilePicture: element(postItemProfilePictureMatcher), + postItemProfilePictureUserStatus: element(postItemProfilePictureUserStatusMatcher), + }; + }; +} + +const post = new Post(); +export default post; diff --git a/detox/e2e/support/ui/component/post_draft.ts b/detox/e2e/support/ui/component/post_draft.ts new file mode 100644 index 000000000..4e3168a07 --- /dev/null +++ b/detox/e2e/support/ui/component/post_draft.ts @@ -0,0 +1,30 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class PostDraft { + testID = { + postDraftSuffix: 'post_draft', + postDraftArchivedSuffix: 'post_draft.archived', + postDraftReadOnlySuffix: 'post_draft.read_only', + postInputSuffix: 'post_draft.post.input', + }; + + getPostDraft = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.postDraftSuffix}`)); + }; + + getPostDraftArchived = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.postDraftArchivedSuffix}`)); + }; + + getPostDraftReadOnly = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.postDraftReadOnlySuffix}`)); + }; + + getPostInput = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.postInputSuffix}`)); + }; +} + +const postDraft = new PostDraft(); +export default postDraft; diff --git a/detox/e2e/support/ui/component/post_list.ts b/detox/e2e/support/ui/component/post_list.ts new file mode 100644 index 000000000..268843e09 --- /dev/null +++ b/detox/e2e/support/ui/component/post_list.ts @@ -0,0 +1,83 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import Post from './post'; + +class PostList { + testID: any; + + constructor(screenPrefix: string) { + this.testID = { + flatList: `${screenPrefix}post_list.flat_list`, + moreMessagesButton: `${screenPrefix}post_list.more_messages_button`, + newMessagesDivider: `${screenPrefix}post_list.new_messages_line`, + postListPostItem: `${screenPrefix}post_list.post`, + }; + } + + getFlatList = () => { + return element(by.id(this.testID.flatList)); + }; + + getMoreMessagesButton = () => { + return element(by.id(this.testID.moreMessagesButton)); + }; + + getNewMessagesDivider = () => { + return element(by.id(this.testID.newMessagesDivider)); + }; + + getPost = (postId: string, postMessage: string, postProfileOptions = {}) => { + const { + postItem, + postItemBlockQuote, + postItemEmoji, + postItemHeaderCommentedOn, + postItemHeaderDateTime, + postItemHeaderDisplayName, + postItemHeaderBotTag, + postItemHeaderGuestTag, + postItemHeaderReply, + postItemHeaderReplyCount, + postItemImage, + postItemMessage, + postItemPreHeaderText, + postItemProfilePicture, + postItemProfilePictureUserStatus, + postItemShowLessButton, + postItemShowMoreButton, + postItemTable, + postItemTableExpandButton, + postItemThematicBreak, + } = Post.getPost(this.testID.postListPostItem, postId, postMessage, postProfileOptions); + + return { + postListPostItem: postItem, + postListPostItemBlockQuote: postItemBlockQuote, + postListPostItemEmoji: postItemEmoji, + postListPostItemHeaderCommentedOn: postItemHeaderCommentedOn, + postListPostItemHeaderDateTime: postItemHeaderDateTime, + postListPostItemHeaderDisplayName: postItemHeaderDisplayName, + postListPostItemHeaderBotTag: postItemHeaderBotTag, + postListPostItemHeaderGuestTag: postItemHeaderGuestTag, + postListPostItemHeaderReply: postItemHeaderReply, + postListPostItemHeaderReplyCount: postItemHeaderReplyCount, + postListPostItemImage: postItemImage, + postListPostItemMessage: postItemMessage, + postListPostItemPreHeaderText: postItemPreHeaderText, + postListPostItemProfilePicture: postItemProfilePicture, + postListPostItemProfilePictureUserStatus: postItemProfilePictureUserStatus, + postListPostItemShowLessButton: postItemShowLessButton, + postListPostItemShowMoreButton: postItemShowMoreButton, + postListPostItemTable: postItemTable, + postListPostItemTableExpandButton: postItemTableExpandButton, + postListPostItemThematicBreak: postItemThematicBreak, + }; + }; + + getPostMessageAtIndex = (index: number) => { + return Post.getPostMessage(this.testID.postListPostItem).atIndex(index); + }; +} + +export default PostList; diff --git a/detox/e2e/support/ui/component/profile_picture.ts b/detox/e2e/support/ui/component/profile_picture.ts new file mode 100644 index 000000000..ef3a05423 --- /dev/null +++ b/detox/e2e/support/ui/component/profile_picture.ts @@ -0,0 +1,21 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class ProfilePicture { + testID = { + userStatusIconPrefix: 'user_status.icon.', + }; + + getProfilePictureItemMatcher = (profilePictureSourcePrefix: string, userId: string) => { + const profilePictureTestID = `${profilePictureSourcePrefix}${userId}`; + return by.id(profilePictureTestID); + }; + + getProfilePictureItemUserStatusMatcher(profilePictureItemMatcher: Detox.NativeMatcher, userStatus = 'online') { + const userStatusIconTestID = `${this.testID.userStatusIconPrefix}${userStatus}`; + return by.id(userStatusIconTestID).withAncestor(profilePictureItemMatcher); + } +} + +const profilePicture = new ProfilePicture(); +export default profilePicture; diff --git a/detox/e2e/support/ui/component/send_button.ts b/detox/e2e/support/ui/component/send_button.ts new file mode 100644 index 000000000..5a2c07466 --- /dev/null +++ b/detox/e2e/support/ui/component/send_button.ts @@ -0,0 +1,20 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class SendButton { + testID = { + sendButtonSuffix: 'post_draft.send_action.send.button', + sendButtonDisabledSuffix: 'post_draft.send_action.send.button.disabled', + }; + + getSendButton = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.sendButtonSuffix}`)); + }; + + getSendButtonDisabled = (screenPrefix: string) => { + return element(by.id(`${screenPrefix}${this.testID.sendButtonDisabledSuffix}`)); + }; +} + +const sendButton = new SendButton(); +export default sendButton; diff --git a/detox/e2e/support/ui/screen/channel.ts b/detox/e2e/support/ui/screen/channel.ts index 89210c580..41be4a968 100644 --- a/detox/e2e/support/ui/screen/channel.ts +++ b/detox/e2e/support/ui/screen/channel.ts @@ -1,13 +1,27 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {NavigationHeader} from '@support/ui/component'; -import {ChannelListScreen} from '@support/ui/screen'; +import { + CameraQuickAction, + FileQuickAction, + ImageQuickAction, + InputQuickAction, + NavigationHeader, + PostDraft, + PostList, + SendButton, +} from '@support/ui/component'; +import { + ChannelListScreen, + PostOptionsScreen, + ThreadScreen, +} from '@support/ui/screen'; import {timeouts} from '@support/utils'; import {expect} from 'detox'; class ChannelScreen { testID = { + channelScreenPrefix: 'channel.', channelScreen: 'channel.screen', introDisplayName: 'channel_post_list.intro.display_name', introOptionAddPeopleItem: 'channel_post_list.intro.option_item.add_people', @@ -20,13 +34,49 @@ class ChannelScreen { introOptionAddPeopleItem = element(by.id(this.testID.introOptionAddPeopleItem)); introOptionSetHeaderItem = element(by.id(this.testID.introOptionSetHeaderItem)); introOptionChannelDetailsItem = element(by.id(this.testID.introOptionChannelDetailsItem)); + + // convenience props backButton = NavigationHeader.backButton; headerTitle = NavigationHeader.headerTitle; + 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); + + postList = new PostList(this.testID.channelScreenPrefix); getIntroOptionItemLabel = (introOptionItemTestId: string) => { return element(by.id(`${introOptionItemTestId}.label`)); }; + getMoreMessagesButton = () => { + return this.postList.getMoreMessagesButton(); + }; + + getNewMessagesDivider = () => { + return this.postList.getNewMessagesDivider(); + }; + + getPostListPostItem = (postId: string, text: string, postProfileOptions: any = {}) => { + return this.postList.getPost(postId, text, postProfileOptions); + }; + + getPostMessageAtIndex = (index: number) => { + return this.postList.getPostMessageAtIndex(index); + }; + toBeVisible = async () => { await waitFor(this.channelScreen).toExist().withTimeout(timeouts.TEN_SEC); @@ -44,6 +94,48 @@ class ChannelScreen { await this.backButton.tap(); await expect(this.channelScreen).not.toBeVisible(); }; + + openPostOptionsFor = async (postId: string, text: string) => { + const {postListPostItem} = this.getPostListPostItem(postId, text); + await expect(postListPostItem).toBeVisible(); + + // # Open post options + await postListPostItem.longPress(); + await PostOptionsScreen.toBeVisible(); + }; + + openReplyThreadFor = async (postId: string, text: string) => { + await this.openPostOptionsFor(postId, text); + + // # Open reply thread screen + await PostOptionsScreen.replyPostOption.tap(); + await ThreadScreen.toBeVisible(); + }; + + postMessage = async (message: string) => { + // # Post message + await this.postInput.tap(); + await this.postInput.replaceText(message); + await this.tapSendButton(); + }; + + tapSendButton = async () => { + // # Tap send button + await this.sendButton.tap(); + await expect(this.sendButton).not.toExist(); + await expect(this.sendButtonDisabled).toBeVisible(); + }; + + hasPostMessage = async (postId: string, postMessage: string) => { + const {postListPostItem} = this.getPostListPostItem(postId, postMessage); + await expect(postListPostItem).toBeVisible(); + }; + + hasPostMessageAtIndex = async (index: number, postMessage: string) => { + await expect( + this.getPostMessageAtIndex(index), + ).toHaveText(postMessage); + }; } const channelScreen = new ChannelScreen(); diff --git a/detox/e2e/support/ui/screen/channel_list.ts b/detox/e2e/support/ui/screen/channel_list.ts index 6027e4d81..b446db817 100644 --- a/detox/e2e/support/ui/screen/channel_list.ts +++ b/detox/e2e/support/ui/screen/channel_list.ts @@ -25,6 +25,8 @@ class ChannelListScreen { headerPlusButton = element(by.id(this.testID.headerPlusButton)); findChannelsInput = element(by.id(this.testID.findChannelsInput)); threadsButton = element(by.id(this.testID.threadsButton)); + + // convenience props browseChannelsItem = PlusMenu.browseChannelsItem; createNewChannelItem = PlusMenu.createNewChannelItem; openDirectMessageItem = PlusMenu.openDirectMessageItem; diff --git a/detox/e2e/support/ui/screen/create_or_edit_channel.ts b/detox/e2e/support/ui/screen/create_or_edit_channel.ts new file mode 100644 index 000000000..e238f1511 --- /dev/null +++ b/detox/e2e/support/ui/screen/create_or_edit_channel.ts @@ -0,0 +1,80 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import { + ChannelScreen, + ChannelListScreen, +} from '@support/ui/screen'; +import {timeouts} from '@support/utils'; +import {expect} from 'detox'; + +class CreateOrEditChannelScreen { + testID = { + createOrEditChannelScreen: 'create_or_edit_channel.screen', + closeButton: 'close.create_or_edit_channel.button', + createButton: 'create_or_edit_channel.create.button', + saveButton: 'create_or_edit_channel.save.button', + scrollView: 'create_or_edit_channel.scrollview', + makePrivateToggledOff: 'channel_info_form.make_private.toggled.false', + makePrivateToggledOn: 'channel_info_form.make_private.toggled.true', + makePrivateDescription: 'channel_info_form.make_private.description', + displayNameInput: 'channel_info_form.display_name.input', + purposeInput: 'channel_info_form.purpose.input', + purposeDescription: 'channel_info_form.purpose.description', + headerInput: 'channel_info_form.header.input', + headerDescription: 'channel_info_form.header.description', + }; + + createOrEditChannelScreen = element(by.id(this.testID.createOrEditChannelScreen)); + closeButton = element(by.id(this.testID.closeButton)); + createButton = element(by.id(this.testID.createButton)); + saveButton = element(by.id(this.testID.saveButton)); + scrollView = element(by.id(this.testID.scrollView)); + makePrivateToggledOff = element(by.id(this.testID.makePrivateToggledOff)); + makePrivateToggledOn = element(by.id(this.testID.makePrivateToggledOn)); + makePrivateDescription = element(by.id(this.testID.makePrivateDescription)); + displayNameInput = element(by.id(this.testID.displayNameInput)); + purposeInput = element(by.id(this.testID.purposeInput)); + purposeDescription = element(by.id(this.testID.purposeDescription)); + headerInput = element(by.id(this.testID.headerInput)); + headerDescription = element(by.id(this.testID.headerDescription)); + + toBeVisible = async () => { + await waitFor(this.createOrEditChannelScreen).toExist().withTimeout(timeouts.TEN_SEC); + + return this.createOrEditChannelScreen; + }; + + openCreateChannel = async () => { + // # Open create channel screen + await ChannelListScreen.headerPlusButton.tap(); + await ChannelListScreen.createNewChannelItem.tap(); + + return this.toBeVisible(); + }; + + openEditChannel = async () => { + // # Open edit channel screen + await ChannelScreen.introOptionSetHeaderItem.tap(); + + return this.toBeVisible(); + }; + + close = async () => { + await this.closeButton.tap(); + await expect(this.createOrEditChannelScreen).not.toBeVisible(); + }; + + toggleMakePrivateOn = async () => { + await this.makePrivateToggledOff.tap(); + await expect(this.makePrivateToggledOn).toBeVisible(); + }; + + toggleMakePrivateOff = async () => { + await this.makePrivateToggledOn.tap(); + await expect(this.makePrivateToggledOff).toBeVisible(); + }; +} + +const createOrEditChannelScreen = new CreateOrEditChannelScreen(); +export default createOrEditChannelScreen; diff --git a/detox/e2e/support/ui/screen/home.ts b/detox/e2e/support/ui/screen/home.ts index aa4af4803..c46dd63ea 100644 --- a/detox/e2e/support/ui/screen/home.ts +++ b/detox/e2e/support/ui/screen/home.ts @@ -22,7 +22,7 @@ class HomeScreen { accountTab = element(by.id(this.testID.accountTab)); toBeVisible = async () => { - await waitFor(this.channelListTab).toBeVisible().withTimeout(timeouts.TEN_SEC); + await waitFor(this.channelListTab).toExist().withTimeout(timeouts.TEN_SEC); return this.channelListTab; }; diff --git a/detox/e2e/support/ui/screen/index.ts b/detox/e2e/support/ui/screen/index.ts index 723217e72..7adc1b75c 100644 --- a/detox/e2e/support/ui/screen/index.ts +++ b/detox/e2e/support/ui/screen/index.ts @@ -6,11 +6,14 @@ import BrowseChannelsScreen from './browse_channels'; import ChannelScreen from './channel'; import ChannelListScreen from './channel_list'; import CreateDirectMessageScreen from './create_direct_message'; +import CreateOrEditChannelScreen from './create_or_edit_channel'; import EditServerScreen from './edit_server'; import HomeScreen from './home'; import LoginScreen from './login'; +import PostOptionsScreen from './post_options'; import ServerScreen from './server'; import ServerListScreen from './server_list'; +import ThreadScreen from './thread'; export { AccountScreen, @@ -18,9 +21,12 @@ export { ChannelScreen, ChannelListScreen, CreateDirectMessageScreen, + CreateOrEditChannelScreen, EditServerScreen, HomeScreen, LoginScreen, + PostOptionsScreen, ServerScreen, ServerListScreen, + ThreadScreen, }; diff --git a/detox/e2e/support/ui/screen/post_options.ts b/detox/e2e/support/ui/screen/post_options.ts new file mode 100644 index 000000000..780fb232f --- /dev/null +++ b/detox/e2e/support/ui/screen/post_options.ts @@ -0,0 +1,79 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Alert} from '@support/ui/component'; +import {timeouts, wait} from '@support/utils'; +import {expect} from 'detox'; + +class PostOptionsScreen { + testID = { + reactionEmojiPrefix: 'post_options.reaction_bar.reaction.', + postOptionsScreen: 'post_options.screen', + pickReaction: 'post_options.reaction_bar.pick_reaction', + replyPostOption: 'post_options.reply.post.option', + copyLinkOption: 'post_options.copy.permalink.option', + saveChannelOption: 'post_options.save.channel.option', + unsaveChannelOption: 'post_options.unsave.channel.option', + copyTextOption: 'post_options.copy.text.option', + pinChannelOption: 'post_options.pin.channel.option', + unpinChannelOption: 'post_options.unpin.channel.option', + editPostOption: 'post_options.edit.post.option', + deletePostOption: 'post_options.delete.post.option', + followThreadOption: 'post_options.follow.thread.option', + markUnreadOption: 'post_options.mark.unread.option', + }; + + postOptionsScreen = element(by.id(this.testID.postOptionsScreen)); + pickReaction = element(by.id(this.testID.pickReaction)); + replyPostOption = element(by.id(this.testID.replyPostOption)); + copyLinkOption = element(by.id(this.testID.copyLinkOption)); + saveChannelOption = element(by.id(this.testID.saveChannelOption)); + unsaveChannelOption = element(by.id(this.testID.unsaveChannelOption)); + copyTextOption = element(by.id(this.testID.copyTextOption)); + pinChannelOption = element(by.id(this.testID.pinChannelOption)); + unpinChannelOption = element(by.id(this.testID.unpinChannelOption)); + editPostOption = element(by.id(this.testID.editPostOption)); + deletePostOption = element(by.id(this.testID.deletePostOption)); + followThreadOption = element(by.id(this.testID.followThreadOption)); + markUnreadOption = element(by.id(this.testID.markUnreadOption)); + + getReactionEmoji = (emojiName: string) => { + return element(by.id(`${this.testID.reactionEmojiPrefix}${emojiName}`)); + }; + + toBeVisible = async () => { + await waitFor(this.postOptionsScreen).toExist().withTimeout(timeouts.TEN_SEC); + + return postOptionsScreen; + }; + + close = async () => { + await this.postOptionsScreen.tap({x: 5, y: 10}); + await expect(this.postOptionsScreen).not.toBeVisible(); + }; + + deletePost = async ({confirm = true} = {}) => { + await this.deletePostOption.tap({x: 1, y: 1}); + const { + deletePostTitle, + cancelButton, + deleteButton, + } = Alert; + await expect(deletePostTitle).toBeVisible(); + await expect(cancelButton).toBeVisible(); + await expect(deleteButton).toBeVisible(); + if (confirm) { + await deleteButton.tap(); + await wait(timeouts.ONE_SEC); + await expect(this.postOptionsScreen).not.toBeVisible(); + } else { + await cancelButton.tap(); + await wait(timeouts.ONE_SEC); + await expect(this.postOptionsScreen).toBeVisible(); + await this.close(); + } + }; +} + +const postOptionsScreen = new PostOptionsScreen(); +export default postOptionsScreen; diff --git a/detox/e2e/support/ui/screen/thread.ts b/detox/e2e/support/ui/screen/thread.ts new file mode 100644 index 000000000..e30080998 --- /dev/null +++ b/detox/e2e/support/ui/screen/thread.ts @@ -0,0 +1,31 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {NavigationHeader} from '@support/ui/component'; +import {timeouts} from '@support/utils'; +import {expect} from 'detox'; + +class ThreadScreen { + testID = { + threadScreen: 'channel.screen', + }; + + threadScreen = element(by.id(this.testID.threadScreen)); + + // convenience props + backButton = NavigationHeader.backButton; + + toBeVisible = async () => { + await waitFor(this.threadScreen).toExist().withTimeout(timeouts.TEN_SEC); + + return this.threadScreen; + }; + + back = async () => { + await this.backButton.tap(); + await expect(this.threadScreen).not.toBeVisible(); + }; +} + +const threadScreen = new ThreadScreen(); +export default threadScreen; diff --git a/detox/e2e/test/channels/browse_channels.e2e.ts b/detox/e2e/test/channels/browse_channels.e2e.ts index 911b16fcc..b74bb6687 100644 --- a/detox/e2e/test/channels/browse_channels.e2e.ts +++ b/detox/e2e/test/channels/browse_channels.e2e.ts @@ -77,9 +77,9 @@ describe('Channels - Browse Channels', () => { await expect(BrowseChannelsScreen.getChannelItemDisplayName(channel.name)).toHaveText(channel.display_name); // # Tap on the new channel item - await BrowseChannelsScreen.getChannelItem(channel.name).tap(); + await BrowseChannelsScreen.getChannelItem(channel.name).multiTap(2); - // * Verify on new channel screen + // * Verify on newly joined channel screen await ChannelScreen.toBeVisible(); await expect(ChannelScreen.headerTitle).toHaveText(channel.display_name); await expect(ChannelScreen.introDisplayName).toHaveText(channel.display_name); @@ -88,7 +88,7 @@ describe('Channels - Browse Channels', () => { await ChannelScreen.back(); await ChannelListScreen.toBeVisible(); - // * Verify new channel is added to channel list + // * Verify newly joined channel is added to channel list await expect(ChannelListScreen.getChannelListItemDisplayName(channelsCategory, channel.name)).toBeVisible(); }); }); diff --git a/detox/e2e/test/channels/channel_list.e2e.ts b/detox/e2e/test/channels/channel_list.e2e.ts index 5e356a96a..1813a0a25 100644 --- a/detox/e2e/test/channels/channel_list.e2e.ts +++ b/detox/e2e/test/channels/channel_list.e2e.ts @@ -17,6 +17,7 @@ import { ChannelScreen, ChannelListScreen, CreateDirectMessageScreen, + CreateOrEditChannelScreen, HomeScreen, LoginScreen, ServerScreen, @@ -142,8 +143,16 @@ describe('Channels - Channel List', () => { await CreateDirectMessageScreen.close(); }); - xit('MM-T4728_6 - should be able to go to create channel screen', async () => { - // NOT YET IMPLEMENTED + it('MM-T4728_6 - should be able to go to create channel screen', async () => { + // # Tap on plus menu button and tap on create new channel item + await ChannelListScreen.headerPlusButton.tap(); + await ChannelListScreen.createNewChannelItem.tap(); + + // * Verify on create channel screen + await CreateOrEditChannelScreen.toBeVisible(); + + // # Go back to channel list screen + await CreateOrEditChannelScreen.close(); }); xit('MM-T4728_7 - should be able to go to threads screen', async () => { diff --git a/detox/e2e/test/channels/channel_post_list.e2e.ts b/detox/e2e/test/channels/channel_post_list.e2e.ts new file mode 100644 index 000000000..77831bf7d --- /dev/null +++ b/detox/e2e/test/channels/channel_post_list.e2e.ts @@ -0,0 +1,98 @@ +// 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 { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelScreen, + ChannelListScreen, + HomeScreen, + LoginScreen, + PostOptionsScreen, + ServerScreen, +} from '@support/ui/screen'; +import {getRandomId} from '@support/utils'; +import {expect} from 'detox'; + +describe('Channels - Channel Post List', () => { + const serverOneDisplayName = 'Server 1'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4773_1 - should match elements on channel screen', async () => { + // # Open a channel screen + await ChannelScreen.open('channels', testChannel.name); + + // * Verify basic elements on channel screen + await expect(ChannelScreen.backButton).toBeVisible(); + await expect(ChannelScreen.headerTitle).toHaveText(testChannel.display_name); + await expect(ChannelScreen.introDisplayName).toHaveText(testChannel.display_name); + await expect(ChannelScreen.introOptionAddPeopleItem).toBeVisible(); + await expect(ChannelScreen.introOptionSetHeaderItem).toBeVisible(); + await expect(ChannelScreen.introOptionChannelDetailsItem).toBeVisible(); + await expect(ChannelScreen.postList.getFlatList()).toBeVisible(); + await expect(ChannelScreen.postDraft).toBeVisible(); + await expect(ChannelScreen.postInput).toBeVisible(); + await expect(ChannelScreen.atInputQuickAction).toBeVisible(); + await expect(ChannelScreen.slashInputQuickAction).toBeVisible(); + await expect(ChannelScreen.fileQuickAction).toBeVisible(); + await expect(ChannelScreen.imageQuickAction).toBeVisible(); + await expect(ChannelScreen.cameraQuickAction).toBeVisible(); + await expect(ChannelScreen.sendButtonDisabled).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4773_2 - should be able to add a message to post list and delete a message from post list', async () => { + // # Open a channel screen and post a message + const message = `Message ${getRandomId()}`; + await ChannelScreen.open('channels', testChannel.name); + await ChannelScreen.postMessage(message); + + // * Verify message is added to post list + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItem).toExist(); + + // # Open post options for the message that was just posted, tap delete option and confirm + await ChannelScreen.openPostOptionsFor(post.id, message); + await PostOptionsScreen.deletePost({confirm: true}); + + // * Verify message is deleted from post list + await expect(postListPostItem).not.toExist(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/channels/create_and_edit_header.e2e.ts b/detox/e2e/test/channels/create_and_edit_header.e2e.ts new file mode 100644 index 000000000..030ecc7eb --- /dev/null +++ b/detox/e2e/test/channels/create_and_edit_header.e2e.ts @@ -0,0 +1,139 @@ +// 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 {Setup} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + ChannelScreen, + CreateOrEditChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {getRandomId} from '@support/utils'; +import {expect} from 'detox'; + +describe('Channels - Create Channel and Edit Channel Header', () => { + const serverOneDisplayName = 'Server 1'; + + beforeAll(async () => { + const {user} = await Setup.apiInit(siteOneUrl); + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4731_1 - should match elements on create channel screen', async () => { + // # Open create channel screen + await CreateOrEditChannelScreen.openCreateChannel(); + + // * Verify basic elements on create channel screen + await expect(CreateOrEditChannelScreen.closeButton).toBeVisible(); + await expect(CreateOrEditChannelScreen.createButton).toBeVisible(); + await expect(CreateOrEditChannelScreen.makePrivateToggledOff).toBeVisible(); + await expect(CreateOrEditChannelScreen.makePrivateDescription).toHaveText('When a channel is set to private, only invited team members can access and participate in that channel'); + await expect(CreateOrEditChannelScreen.displayNameInput).toBeVisible(); + await expect(CreateOrEditChannelScreen.purposeInput).toBeVisible(); + await expect(CreateOrEditChannelScreen.purposeDescription).toHaveText('Describe how this channel should be used.'); + await expect(CreateOrEditChannelScreen.headerInput).toBeVisible(); + await expect(CreateOrEditChannelScreen.headerDescription).toHaveText('Specify text to appear in the channel header beside the channel name. For example, include frequently used links by typing link text [Link Title](http://example.com).'); + + // # Go back to channel list screen + await CreateOrEditChannelScreen.close(); + }); + + it('MM-T4731_2 - should be able to create a public channel and edit the channel header', async () => { + // # Open create channel screen, toggle make private off, fill out channel info, and tap create button + const suffix = getRandomId(); + const displayName = `Channel ${suffix}`; + const purpose = `Purpose ${suffix}`; + const header = `Header ${suffix}`; + await CreateOrEditChannelScreen.openCreateChannel(); + await expect(CreateOrEditChannelScreen.makePrivateToggledOff).toBeVisible(); + await CreateOrEditChannelScreen.displayNameInput.replaceText(displayName); + await CreateOrEditChannelScreen.purposeInput.replaceText(purpose); + await CreateOrEditChannelScreen.headerInput.replaceText(header); + await CreateOrEditChannelScreen.createButton.tap(); + + // * Verify on newly created public channel + await ChannelScreen.toBeVisible(); + await expect(ChannelScreen.headerTitle).toHaveText(displayName); + await expect(ChannelScreen.introDisplayName).toHaveText(displayName); + + // # Tap on set header option to edit the channel header + await ChannelScreen.introOptionSetHeaderItem.tap(); + + // * Verify channel header is correct + await expect(CreateOrEditChannelScreen.headerInput).toHaveValue(header); + + // # Edit the channel header, save, and re-open edit channel header screen + await CreateOrEditChannelScreen.headerInput.replaceText(`${header} edit`); + await CreateOrEditChannelScreen.saveButton.tap(); + await CreateOrEditChannelScreen.openEditChannel(); + + // * Verify channel header has new value + await expect(CreateOrEditChannelScreen.headerInput).toHaveValue(`${header} edit`); + + // # Go back to channel list screen + await CreateOrEditChannelScreen.close(); + await ChannelScreen.back(); + }); + + it('MM-T4731_3 - should be able to create a private channel and edit the channel header', async () => { + // # Open create channel screen, toggle make private on, fill out channel info, and tap create button + const suffix = getRandomId(); + const displayName = `Channel ${suffix}`; + const purpose = `Purpose ${suffix}`; + const header = `Header ${suffix}`; + await CreateOrEditChannelScreen.openCreateChannel(); + await CreateOrEditChannelScreen.toggleMakePrivateOn(); + await CreateOrEditChannelScreen.displayNameInput.replaceText(displayName); + await CreateOrEditChannelScreen.purposeInput.replaceText(purpose); + await CreateOrEditChannelScreen.headerInput.replaceText(header); + await CreateOrEditChannelScreen.createButton.tap(); + + // * Verify on newly created private channel + await ChannelScreen.toBeVisible(); + await expect(ChannelScreen.headerTitle).toHaveText(displayName); + await expect(ChannelScreen.introDisplayName).toHaveText(displayName); + + // # Tap on set header option to edit the channel header + await ChannelScreen.introOptionSetHeaderItem.tap(); + + // * Verify channel header is correct + await expect(CreateOrEditChannelScreen.headerInput).toHaveValue(header); + + // # Edit the channel header, save, and re-open edit channel header screen + await CreateOrEditChannelScreen.headerInput.replaceText(`${header} edit`); + await CreateOrEditChannelScreen.saveButton.tap(); + await CreateOrEditChannelScreen.openEditChannel(); + + // * Verify channel header has new value + await expect(CreateOrEditChannelScreen.headerInput).toHaveValue(`${header} edit`); + + // # Go back to channel list screen + await CreateOrEditChannelScreen.close(); + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/channels/create_direct_message.e2e.ts b/detox/e2e/test/channels/create_direct_message.e2e.ts index 8ff9f5c2b..d4ea82288 100644 --- a/detox/e2e/test/channels/create_direct_message.e2e.ts +++ b/detox/e2e/test/channels/create_direct_message.e2e.ts @@ -8,6 +8,7 @@ // ******************************************************************* import { + Channel, Setup, Team, User, @@ -30,10 +31,12 @@ describe('Channels - Create Direct Message', () => { const serverOneDisplayName = 'Server 1'; const directMessagesCategory = 'direct_messages'; let testTeam: any; + let testUser: any; beforeAll(async () => { const {team, user} = await Setup.apiInit(siteOneUrl); testTeam = team; + testUser = user; // # Log in to server await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); @@ -75,7 +78,7 @@ describe('Channels - Create Direct Message', () => { // # Open create direct message screen and search for the new user await CreateDirectMessageScreen.open(); - await CreateDirectMessageScreen.searchInput.replaceText(newUser.username); + await CreateDirectMessageScreen.searchInput.replaceText(newUserDisplayName); // * Verify search returns the new user item await expect(CreateDirectMessageScreen.getUserItemDisplayName(newUser.id)).toBeVisible(); @@ -99,7 +102,8 @@ describe('Channels - Create Direct Message', () => { await ChannelListScreen.toBeVisible(); // * Verify direct message channel for the new user is added to direct message list - await expect(ChannelListScreen.getChannelListItemDisplayName(directMessagesCategory, newUser.id)).toHaveText(newUserDisplayName); + const {channel: directMessageChannel} = await Channel.apiCreateDirectChannel(siteOneUrl, [testUser.id, newUser.id]); + await expect(ChannelListScreen.getChannelListItemDisplayName(directMessagesCategory, directMessageChannel.name)).toHaveText(newUserDisplayName); }); it('MM-T4730_3 - should be able to create a group message', async () => { @@ -112,8 +116,8 @@ describe('Channels - Create Direct Message', () => { // * Verify no group message channel for the new users appears on channel list screen const firstNewUserDisplayName = firstNewUser.username; const secondNewUserDisplayName = secondNewUser.username; - const groupDisplayName = `${firstNewUserDisplayName}, ${secondNewUserDisplayName}`; - await expect(ChannelListScreen.getChannelListItemDisplayName(directMessagesCategory, groupDisplayName)).not.toBeVisible(); + const groupDisplayName = `${firstNewUserDisplayName}, ${secondNewUserDisplayName}, ${testUser.username}`; + await expect(element(by.text(groupDisplayName))).not.toBeVisible(); // # Open create direct message screen, search for the first new user and tap on the first new user item await CreateDirectMessageScreen.open(); @@ -143,6 +147,6 @@ describe('Channels - Create Direct Message', () => { await ChannelListScreen.toBeVisible(); // * Verify group message channel for the other two new users is added to direct message list - await expect(ChannelListScreen.getChannelListItemDisplayName(directMessagesCategory, `${firstNewUser.id}__${secondNewUser.id}`)).toHaveText(groupDisplayName); + await expect(element(by.text(groupDisplayName))).toBeVisible(); }); }); diff --git a/detox/e2e/test/smoke_test/channels.e2e.ts b/detox/e2e/test/smoke_test/channels.e2e.ts new file mode 100644 index 000000000..0a767a838 --- /dev/null +++ b/detox/e2e/test/smoke_test/channels.e2e.ts @@ -0,0 +1,130 @@ +// 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 { + Channel, + Post, + Setup, + Team, + User, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + BrowseChannelsScreen, + ChannelScreen, + ChannelListScreen, + CreateDirectMessageScreen, + CreateOrEditChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {getRandomId} from '@support/utils'; +import {expect} from 'detox'; + +describe('Smoke Test - Channels', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + let testTeam: any; + + beforeAll(async () => { + const {channel, team, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + testTeam = team; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4774_1 - should be able to join a new channel and switch to an existing channel', async () => { + // # As admin, create a new channel so that user can join, then open browse channels screen and join the new channel + const {channel} = await Channel.apiCreateChannel(siteOneUrl, {teamId: testTeam.id}); + await BrowseChannelsScreen.open(); + await BrowseChannelsScreen.searchInput.replaceText(channel.name); + await BrowseChannelsScreen.getChannelItem(channel.name).multiTap(2); + + // * Verify on newly joined channel screen + await ChannelScreen.toBeVisible(); + await expect(ChannelScreen.headerTitle).toHaveText(channel.display_name); + await expect(ChannelScreen.introDisplayName).toHaveText(channel.display_name); + + // # Go back to channel list screen and switch to an existing channel + await ChannelScreen.back(); + await ChannelScreen.open(channelsCategory, testChannel.name); + + // * Verify on the other channel screen + await ChannelScreen.toBeVisible(); + await expect(ChannelScreen.headerTitle).toHaveText(testChannel.display_name); + await expect(ChannelScreen.introDisplayName).toHaveText(testChannel.display_name); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4774_2 - should be able to create a channel and create a direct message', async () => { + // # Open create channel screen and create a new channel + const displayName = `Channel ${getRandomId()}`; + await CreateOrEditChannelScreen.openCreateChannel(); + await CreateOrEditChannelScreen.displayNameInput.replaceText(displayName); + await CreateOrEditChannelScreen.createButton.tap(); + + // * Verify on newly created public channel + await ChannelScreen.toBeVisible(); + await expect(ChannelScreen.headerTitle).toHaveText(displayName); + await expect(ChannelScreen.introDisplayName).toHaveText(displayName); + + // # As admin, create a new user to open direct message with, then go back to channel list screen, open create direct message screen and open direct message with new user + const {user: newUser} = await User.apiCreateUser(siteOneUrl); + const newUserDisplayName = newUser.username; + await Team.apiAddUserToTeam(siteOneUrl, newUser.id, testTeam.id); + await ChannelScreen.back(); + await CreateDirectMessageScreen.open(); + await CreateDirectMessageScreen.searchInput.replaceText(newUserDisplayName); + await CreateDirectMessageScreen.getUserItem(newUser.id).tap(); + await CreateDirectMessageScreen.startButton.tap(); + + // * Verify on direct message channel screen for the new user + await ChannelScreen.toBeVisible(); + await expect(ChannelScreen.headerTitle).toHaveText(newUserDisplayName); + await expect(ChannelScreen.introDisplayName).toHaveText(newUserDisplayName); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4774_3 - should be able to post a message in a channel', async () => { + // # Open a channel screen and post a message + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + + // * Verify message is posted + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItem).toExist(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/package-lock.json b/package-lock.json index d531aeef7..8af47776d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,6 +56,7 @@ "react-native-create-thumbnail": "1.5.1", "react-native-device-info": "8.7.0", "react-native-document-picker": "8.1.0", + "react-native-dotenv": "3.4.0", "react-native-elements": "3.4.2", "react-native-exception-handler": "2.10.10", "react-native-fast-image": "8.5.11", @@ -118,6 +119,7 @@ "@types/react-native": "0.67.3", "@types/react-native-background-timer": "2.0.0", "@types/react-native-button": "3.0.1", + "@types/react-native-dotenv": "0.2.0", "@types/react-native-share": "3.3.3", "@types/react-native-video": "5.0.13", "@types/react-syntax-highlighter": "13.5.2", @@ -5766,6 +5768,12 @@ "@types/react-native": "*" } }, + "node_modules/@types/react-native-dotenv": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@types/react-native-dotenv/-/react-native-dotenv-0.2.0.tgz", + "integrity": "sha512-ZxX+dU/yoQc0jTk+/NWttkiuXceJyN5FpOSqDl0WynN5GDzxwH7OMruQ47qcY8llo2RD3irjvzJ9BwC8gDiq0A==", + "dev": true + }, "node_modules/@types/react-native-share": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/@types/react-native-share/-/react-native-share-3.3.3.tgz", @@ -9432,6 +9440,14 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dotenv": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", + "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==", + "engines": { + "node": ">=12" + } + }, "node_modules/dtrace-provider": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", @@ -19041,6 +19057,14 @@ } } }, + "node_modules/react-native-dotenv": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/react-native-dotenv/-/react-native-dotenv-3.4.0.tgz", + "integrity": "sha512-Z/pvS0QGSDHcxnUmJ/uHLgC1/DZL/UpKSOHp3mCKyV3vQd385CBahrEBca8pD3eabrsDjg8xO0uRxnRG3s1lbw==", + "dependencies": { + "dotenv": "^16.0.0" + } + }, "node_modules/react-native-elements": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-3.4.2.tgz", @@ -27593,6 +27617,12 @@ "@types/react-native": "*" } }, + "@types/react-native-dotenv": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@types/react-native-dotenv/-/react-native-dotenv-0.2.0.tgz", + "integrity": "sha512-ZxX+dU/yoQc0jTk+/NWttkiuXceJyN5FpOSqDl0WynN5GDzxwH7OMruQ47qcY8llo2RD3irjvzJ9BwC8gDiq0A==", + "dev": true + }, "@types/react-native-share": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/@types/react-native-share/-/react-native-share-3.3.3.tgz", @@ -30487,6 +30517,11 @@ "domhandler": "^4.2.0" } }, + "dotenv": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", + "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==" + }, "dtrace-provider": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", @@ -38027,6 +38062,14 @@ "invariant": "^2.2.4" } }, + "react-native-dotenv": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/react-native-dotenv/-/react-native-dotenv-3.4.0.tgz", + "integrity": "sha512-Z/pvS0QGSDHcxnUmJ/uHLgC1/DZL/UpKSOHp3mCKyV3vQd385CBahrEBca8pD3eabrsDjg8xO0uRxnRG3s1lbw==", + "requires": { + "dotenv": "^16.0.0" + } + }, "react-native-elements": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-3.4.2.tgz", diff --git a/package.json b/package.json index 6c067883f..4a9515c74 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "react-native-create-thumbnail": "1.5.1", "react-native-device-info": "8.7.0", "react-native-document-picker": "8.1.0", + "react-native-dotenv": "3.4.0", "react-native-elements": "3.4.2", "react-native-exception-handler": "2.10.10", "react-native-fast-image": "8.5.11", @@ -116,6 +117,7 @@ "@types/react-native": "0.67.3", "@types/react-native-background-timer": "2.0.0", "@types/react-native-button": "3.0.1", + "@types/react-native-dotenv": "0.2.0", "@types/react-native-share": "3.3.3", "@types/react-native-video": "5.0.13", "@types/react-syntax-highlighter": "13.5.2", diff --git a/env.d.ts b/types/env.d.ts similarity index 85% rename from env.d.ts rename to types/env.d.ts index 1b5ca9042..109580c22 100644 --- a/env.d.ts +++ b/types/env.d.ts @@ -3,4 +3,5 @@ // So that typescript doesn't complain about importing `@env` through react-native-dotenv declare module '@env' { + export const RUNNING_E2E: string; }