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;
}