MM-30286 Detox/E2E: Add e2e test for MM-T3222 and some cleanup (#4959)

* MM-30286 Detox/E2E: Add e2e test for MM-T3222 and some cleanup

* Fix merge issues

* Fix more issues

* Fixed new line

* Remove redundant check

* Updated instance to getElement

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
Joseph Baylon 2020-11-17 10:52:03 -08:00 committed by GitHub
parent 402c312f8f
commit 4f668f0fdf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
89 changed files with 961 additions and 361 deletions

View file

@ -5,7 +5,6 @@ exports[`PasteableTextInput should render pasteable text input 1`] = `
allowFontScaling={true}
onPaste={[Function]}
rejectResponderTermination={true}
testID="post.input"
underlineColorAndroid="transparent"
>
My Text

View file

@ -53,11 +53,11 @@ export class PasteableTextInput extends React.PureComponent {
}
render() {
const {forwardRef, ...props} = this.props;
const {testID, forwardRef, ...props} = this.props;
return (
<TextInput
testID='post.input'
testID={testID}
{...props}
onPaste={this.onPaste}
ref={forwardRef}

View file

@ -34,6 +34,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
export default class Post extends PureComponent {
static propTypes = {
testID: PropTypes.string,
actions: PropTypes.shape({
createPost: PropTypes.func.isRequired,
insertToDraft: PropTypes.func.isRequired,
@ -240,6 +241,7 @@ export default class Post extends PureComponent {
render() {
const {
testID,
channelIsReadOnly,
commentedOnPost,
highlight,
@ -323,7 +325,10 @@ export default class Post extends PureComponent {
const rightColumnStyle = [style.rightColumn, (commentedOnPost && isLastReply && style.rightColumnPadding)];
return (
<View style={[style.postStyle, highlighted, padding(isLandscape)]}>
<View
testID={testID}
style={[style.postStyle, highlighted, padding(isLandscape)]}
>
<TouchableWithFeedback
onPress={this.handlePress}
onLongPress={this.showPostOptions}

View file

@ -13,6 +13,7 @@ exports[`PostDraft Should render the Archived for channelIsArchived 1`] = `
"paddingTop": 10,
}
}
testID="post_draft.archived"
>
<Text>
<Text
@ -116,6 +117,7 @@ exports[`PostDraft Should render the Archived for deactivatedChannel 1`] = `
"paddingTop": 10,
}
}
testID="post_draft.archived"
>
<Text>
<Text
@ -266,6 +268,7 @@ exports[`PostDraft Should render the DraftInput 1`] = `
null,
]
}
testID="post_draft"
>
<RCTScrollView
contentContainerStyle={
@ -316,7 +319,7 @@ exports[`PostDraft Should render the DraftInput 1`] = `
"paddingTop": 6,
}
}
testID="post.input"
testID="post_draft.post.input"
textContentType="none"
underlineColorAndroid="transparent"
/>
@ -534,7 +537,7 @@ exports[`PostDraft Should render the DraftInput 1`] = `
"paddingRight": 8,
}
}
testID="disabled_send.button"
testID="post_draft.send.button.disabled"
>
<View
style={
@ -587,6 +590,7 @@ exports[`PostDraft Should render the ReadOnly for canPost 1`] = `
"paddingHorizontal": 12,
}
}
testID="post_draft.read_only"
>
<Icon
color="#3d3c40"
@ -636,6 +640,7 @@ exports[`PostDraft Should render the ReadOnly for channelIsReadOnly 1`] = `
"paddingHorizontal": 12,
}
}
testID="post_draft.read_only"
>
<Icon
color="#3d3c40"

View file

@ -50,7 +50,10 @@ export default class Archived extends PureComponent {
const style = getStyleSheet(theme);
return (
<View style={style.archivedWrapper}>
<View
testID='post_draft.archived'
style={style.archivedWrapper}
>
<FormattedMarkdownText
{...this.message()}
theme={theme}

View file

@ -29,6 +29,7 @@ const HW_EVENT_IN_SCREEN = ['Channel', 'Thread'];
export default class DraftInput extends PureComponent {
static propTypes = {
testID: PropTypes.string,
registerTypingAnimation: PropTypes.func.isRequired,
addReactionToLatestPost: PropTypes.func.isRequired,
getChannelMemberCountsByGroup: PropTypes.func.isRequired,
@ -395,6 +396,7 @@ export default class DraftInput extends PureComponent {
render() {
const {
testID,
channelDisplayName,
channelId,
cursorPositionEvent,
@ -416,6 +418,7 @@ export default class DraftInput extends PureComponent {
registerTypingAnimation={registerTypingAnimation}
/>
<View
testID={testID}
style={[style.inputWrapper, padding(isLandscape)]}
onLayout={this.handleLayout}
>

View file

@ -90,6 +90,7 @@ export default class PostDraft extends PureComponent {
const draftInput = (
<DraftInput
testID='post_draft'
ref={this.draftInput}
channelId={channelId}
cursorPositionEvent={cursorPositionEvent}

View file

@ -63,7 +63,7 @@ describe('PostDraft', () => {
);
expect(toJSON()).toMatchSnapshot();
expect(getByTestId('post.input')).toBeTruthy();
expect(getByTestId('post_draft.post.input')).toBeTruthy();
expect(queryByText('Close Channel')).toBeNull();
});
@ -79,7 +79,7 @@ describe('PostDraft', () => {
expect(toJSON()).toMatchSnapshot();
// Should not render text input
expect(queryByTestId('post.input')).toBeNull();
expect(queryByTestId('post_draft.post.input')).toBeNull();
// Should match text description
expect(getByText('You are viewing an ')).toBeTruthy();
@ -100,7 +100,7 @@ describe('PostDraft', () => {
expect(toJSON()).toMatchSnapshot();
// Should not render text input
expect(queryByTestId('post.input')).toBeNull();
expect(queryByTestId('post_draft.post.input')).toBeNull();
// Should match text description
expect(getByText('You are viewing an ')).toBeTruthy();
@ -121,7 +121,7 @@ describe('PostDraft', () => {
expect(toJSON()).toMatchSnapshot();
// Should not render text input
expect(queryByTestId('post.input')).toBeNull();
expect(queryByTestId('post_draft.post.input')).toBeNull();
// Should match text description
expect(getByText('This channel is read-only.')).toBeTruthy();
@ -140,7 +140,7 @@ describe('PostDraft', () => {
expect(toJSON()).toMatchSnapshot();
// Should not render text input
expect(queryByTestId('post.input')).toBeNull();
expect(queryByTestId('post_draft.post.input')).toBeNull();
// Should match text description
expect(getByText('This channel is read-only.')).toBeTruthy();

View file

@ -25,6 +25,7 @@ exports[`PostInput should match, full snapshot 1`] = `
"paddingTop": 6,
}
}
testID="post_draft.post.input"
textContentType="none"
underlineColorAndroid="transparent"
/>

View file

@ -278,6 +278,7 @@ export default class PostInput extends PureComponent {
return (
<PasteableTextInput
testID='post_draft.post.input'
ref={this.input}
style={{...style.input, maxHeight}}
onChangeText={this.handleTextChange}

View file

@ -19,6 +19,7 @@ exports[`PostDraft ReadOnly Should match snapshot 1`] = `
"paddingHorizontal": 12,
}
}
testID="post_draft.read_only"
>
<CompassIcon
color="#3d3c40"

View file

@ -17,7 +17,10 @@ const ReadOnlyChannnel = ({theme}: ReadOnlyProps): ReactNode => {
const style = getStyle(theme);
return (
<SafeAreaView style={style.background}>
<View style={style.container}>
<View
testID='post_draft.read_only'
style={style.container}
>
<CompassIcon
name='glasses'
style={style.icon}

View file

@ -8,7 +8,7 @@ exports[`SendAction should change theme backgroundColor to 0.3 opacity 1`] = `
"paddingRight": 8,
}
}
testID="disabled_send.button"
testID="post_draft.send.button.disabled"
>
<View
style={
@ -45,7 +45,7 @@ exports[`SendAction should match snapshot 1`] = `
"paddingRight": 8,
}
}
testID="send.button"
testID="post_draft.send.button"
type="opacity"
>
<View
@ -78,7 +78,7 @@ exports[`SendAction should render theme backgroundColor 1`] = `
"paddingRight": 8,
}
}
testID="send.button"
testID="post_draft.send.button"
type="opacity"
>
<View

View file

@ -16,7 +16,7 @@ function SendButton(props) {
if (props.disabled) {
return (
<View
testID='disabled_send.button'
testID='post_draft.send.button.disabled'
style={style.sendButtonContainer}
>
<View style={[style.sendButton, style.disableButton]}>
@ -32,7 +32,7 @@ function SendButton(props) {
return (
<TouchableWithFeedback
testID='send.button'
testID='post_draft.send.button'
onPress={props.handleSendMessage}
style={style.sendButtonContainer}
type={'opacity'}

View file

@ -546,8 +546,11 @@ export default class PostList extends PureComponent {
}
function PostComponent({postId, highlightPostId, lastPostIndex, index, ...postProps}) {
const testID = `post_list.post.${postId}`;
return (
<Post
testID={testID}
postId={postId}
highlight={highlightPostId === postId}
isLastPost={lastPostIndex === index}

View file

@ -230,7 +230,7 @@ exports[`Reactions Should match snapshot with default emojis 1`] = `
/>
<TouchableWithoutFeedback
onPress={[MockFunction]}
testID="open.reaction_picker"
testID="open.add_reaction.button"
>
<View
style={
@ -492,7 +492,7 @@ exports[`Reactions Should match snapshot with default emojis 2`] = `
/>
<TouchableWithoutFeedback
onPress={[MockFunction]}
testID="open.reaction_picker"
testID="open.add_reaction.button"
>
<View
style={

View file

@ -52,9 +52,11 @@ export default class ReactionButton extends PureComponent {
containerSize,
} = this.props;
const style = getStyleSheet(theme);
const testID = `reaction_picker.reaction_button.${emoji}`;
return (
<TouchableWithoutFeedback
testID={testID}
key={emoji}
onPress={this.handlePress}
>

View file

@ -83,7 +83,7 @@ export default class ReactionPicker extends PureComponent {
{list}
<TouchableWithoutFeedback
onPress={this.props.openReactionScreen}
testID='open.reaction_picker'
testID='open.add_reaction.button'
>
<View
style={[

View file

@ -198,11 +198,13 @@ export default class SettingsSidebarBase extends PureComponent {
contentContainerStyle={style.wrapper}
>
<UserInfo
testID='settings.sidebar.user_info.action'
onPress={this.goToUserProfile}
user={currentUser}
/>
<View style={style.block}>
<DrawerItem
testID='settings.sidebar.status.action'
labelComponent={this.renderUserStatusLabel(currentUser.id)}
leftComponent={this.renderUserStatusIcon(currentUser.id)}
separator={false}
@ -213,6 +215,7 @@ export default class SettingsSidebarBase extends PureComponent {
<View style={style.separator}/>
<View style={style.block}>
<DrawerItem
testID='settings.sidebar.recent_mentions.action'
defaultMessage='Recent Mentions'
i18nId='search_header.title2'
iconName='at'
@ -221,6 +224,7 @@ export default class SettingsSidebarBase extends PureComponent {
theme={theme}
/>
<DrawerItem
testID='settings.sidebar.saved_messages.action'
defaultMessage='Saved Messages'
i18nId='search_header.title3'
iconName='bookmark-outline'
@ -232,6 +236,7 @@ export default class SettingsSidebarBase extends PureComponent {
<View style={style.separator}/>
<View style={style.block}>
<DrawerItem
testID='settings.sidebar.edit_profile.action'
defaultMessage='Edit Profile'
i18nId='mobile.routes.edit_profile'
iconName='pencil-outline'

View file

@ -12,6 +12,7 @@ import ProfilePicture from 'app/components/profile_picture';
export default class UserInfo extends PureComponent {
static propTypes = {
testID: PropTypes.string,
user: PropTypes.object.isRequired,
onPress: PropTypes.func,
theme: PropTypes.object.isRequired,
@ -22,7 +23,7 @@ export default class UserInfo extends PureComponent {
};
render() {
const {user, onPress, theme} = this.props;
const {testID, user, onPress, theme} = this.props;
const {first_name: firstName, last_name: lastName} = user;
const style = getStyleSheet(theme);
@ -36,7 +37,10 @@ export default class UserInfo extends PureComponent {
}
return (
<TouchableOpacity onPress={onPress}>
<TouchableOpacity
testID={testID}
onPress={onPress}
>
<View style={style.container}>
<ProfilePicture
size={48}

View file

@ -79,6 +79,7 @@ exports[`channelInfo should match snapshot 1`] = `
<Connect(Favorite)
channelId="1234"
isLandscape={false}
testID="channel_info.favorite.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -143,6 +144,7 @@ exports[`channelInfo should match snapshot 1`] = `
<Connect(Mute)
channelId="1234"
isLandscape={false}
testID="channel_info.mute.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -208,6 +210,7 @@ exports[`channelInfo should match snapshot 1`] = `
<Connect(IgnoreMentions)
channelId="1234"
isLandscape={false}
testID="channel_info.ignore_mentions.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -338,6 +341,7 @@ exports[`channelInfo should match snapshot 1`] = `
<Connect(Pinned)
channelId="1234"
isLandscape={false}
testID="channel_info.pinned.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -370,6 +374,7 @@ exports[`channelInfo should match snapshot 1`] = `
/>
<Connect(ManageMembers)
isLandscape={false}
testID="channel_info.manage_members.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -402,6 +407,7 @@ exports[`channelInfo should match snapshot 1`] = `
/>
<Connect(AddMembers)
isLandscape={false}
testID="channel_info.add_members.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -434,6 +440,7 @@ exports[`channelInfo should match snapshot 1`] = `
/>
<Connect(ConvertPrivate)
isLandscape={false}
testID="channel_info.convert_private.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -582,6 +589,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
<Connect(Favorite)
channelId="1234"
isLandscape={false}
testID="channel_info.favorite.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -646,6 +654,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
<Connect(Mute)
channelId="1234"
isLandscape={false}
testID="channel_info.mute.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -711,6 +720,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
<Connect(IgnoreMentions)
channelId="1234"
isLandscape={false}
testID="channel_info.ignore_mentions.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -841,6 +851,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
<Connect(Pinned)
channelId="1234"
isLandscape={false}
testID="channel_info.pinned.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -873,6 +884,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
/>
<Connect(ManageMembers)
isLandscape={false}
testID="channel_info.manage_members.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -905,6 +917,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
/>
<Connect(AddMembers)
isLandscape={false}
testID="channel_info.add_members.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -937,6 +950,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
/>
<Connect(ConvertPrivate)
isLandscape={false}
testID="channel_info.convert_private.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -1008,6 +1022,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
<Connect(Favorite)
channelId="1234"
isLandscape={false}
testID="channel_info.favorite.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -1072,6 +1087,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
<Connect(Mute)
channelId="1234"
isLandscape={false}
testID="channel_info.mute.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -1137,6 +1153,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
<Connect(IgnoreMentions)
channelId="1234"
isLandscape={false}
testID="channel_info.ignore_mentions.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -1201,6 +1218,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
<Connect(Pinned)
channelId="1234"
isLandscape={false}
testID="channel_info.pinned.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -1233,6 +1251,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
/>
<Connect(ManageMembers)
isLandscape={false}
testID="channel_info.manage_members.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -1265,6 +1284,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
/>
<Connect(AddMembers)
isLandscape={false}
testID="channel_info.add_members.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -1297,6 +1317,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
/>
<Connect(ConvertPrivate)
isLandscape={false}
testID="channel_info.convert_private.action"
theme={
Object {
"awayIndicator": "#ffbc42",

View file

@ -12,6 +12,7 @@ import {t} from '@utils/i18n';
import {preventDoubleTap} from '@utils/tap';
interface AddMembersProps {
testID?: string;
canManageUsers: boolean;
groupConstrained: boolean;
isLandscape: boolean;
@ -32,13 +33,14 @@ export default class AddMembers extends PureComponent<AddMembersProps> {
});
render() {
const {canManageUsers, groupConstrained, isLandscape, theme} = this.props;
const {testID, canManageUsers, groupConstrained, isLandscape, theme} = this.props;
if (canManageUsers && !groupConstrained) {
return (
<>
<Separator theme={theme}/>
<ChannelInfoRow
testID={testID}
action={this.goToChannelAddMembers}
defaultMessage='Add Members'
icon='account-plus-outline'

View file

@ -110,12 +110,14 @@ export default class ChannelInfo extends PureComponent {
return (
<>
<Favorite
testID='channel_info.favorite.action'
channelId={currentChannel.id}
isLandscape={isLandscape}
theme={theme}
/>
<Separator theme={theme}/>
<Mute
testID='channel_info.mute.action'
channelId={currentChannel.id}
isLandscape={isLandscape}
userId={currentUserId}
@ -123,6 +125,7 @@ export default class ChannelInfo extends PureComponent {
/>
<Separator theme={theme}/>
<IgnoreMentions
testID='channel_info.ignore_mentions.action'
channelId={currentChannel.id}
isLandscape={isLandscape}
theme={theme}
@ -139,19 +142,23 @@ export default class ChannelInfo extends PureComponent {
</>
}
<Pinned
testID='channel_info.pinned.action'
channelId={currentChannel.id}
isLandscape={isLandscape}
theme={theme}
/>
<ManageMembers
testID='channel_info.manage_members.action'
isLandscape={isLandscape}
theme={theme}
/>
<AddMembers
testID='channel_info.add_members.action'
isLandscape={isLandscape}
theme={theme}
/>
<ConvertPrivate
testID='channel_info.convert_private.action'
isLandscape={isLandscape}
theme={theme}
/>

View file

@ -14,6 +14,7 @@ import {t} from '@utils/i18n';
import {preventDoubleTap} from '@utils/tap';
interface ConvertPrivateProps {
testID?: string;
canConvert: boolean;
channelId: string;
convertChannelToPrivate: (channelId: string) => Promise<ActionResult>;
@ -79,7 +80,7 @@ export default class ConvertPrivate extends PureComponent<ConvertPrivateProps> {
});
render() {
const {canConvert, isLandscape, theme} = this.props;
const {testID, canConvert, isLandscape, theme} = this.props;
if (!canConvert) {
return null;
@ -89,6 +90,7 @@ export default class ConvertPrivate extends PureComponent<ConvertPrivateProps> {
<>
<Separator theme={theme}/>
<ChannelInfoRow
testID={testID}
action={this.handleConvertToPrivate}
defaultMessage='Convert to Private Channel'
icon='lock'

View file

@ -9,6 +9,7 @@ import {t} from '@utils/i18n';
import {preventDoubleTap} from '@utils/tap';
interface FavoriteProps {
testID?: string;
channelId: string;
favoriteChannel: (channelId: string) => void;
isFavorite: boolean;
@ -17,7 +18,7 @@ interface FavoriteProps {
theme: Theme;
}
const Favorite = ({channelId, favoriteChannel, isLandscape, isFavorite, unfavoriteChannel, theme}: FavoriteProps) => {
const Favorite = ({testID, channelId, favoriteChannel, isLandscape, isFavorite, unfavoriteChannel, theme}: FavoriteProps) => {
const [favorite, setFavorite] = useState(isFavorite);
const handleFavorite = preventDoubleTap(() => {
@ -28,6 +29,7 @@ const Favorite = ({channelId, favoriteChannel, isLandscape, isFavorite, unfavori
return (
<ChannelInfoRow
testID={testID}
action={handleFavorite}
defaultMessage='Favorite'
detail={favorite}

View file

@ -10,6 +10,7 @@ import {t} from '@utils/i18n';
import {preventDoubleTap} from '@utils/tap';
interface IgnoreMentionsProps {
testID?: string;
channelId: string;
ignore: boolean;
isLandscape: boolean;
@ -18,7 +19,7 @@ interface IgnoreMentionsProps {
userId: string;
}
const IgnoreMentions = ({channelId, ignore, isLandscape, updateChannelNotifyProps, userId, theme}: IgnoreMentionsProps) => {
const IgnoreMentions = ({testID, channelId, ignore, isLandscape, updateChannelNotifyProps, userId, theme}: IgnoreMentionsProps) => {
const [mentions, setMentions] = useState(ignore);
const handleIgnoreChannelMentions = preventDoubleTap(() => {
@ -32,6 +33,7 @@ const IgnoreMentions = ({channelId, ignore, isLandscape, updateChannelNotifyProp
return (
<ChannelInfoRow
testID={testID}
action={handleIgnoreChannelMentions}
defaultMessage='Ignore @channel, @here, @all'
detail={ignore}

View file

@ -12,6 +12,7 @@ import {t} from '@utils/i18n';
import {preventDoubleTap} from '@utils/tap';
interface ManageMembersProps {
testID?: string;
canManageUsers: boolean;
isDirectMessage: boolean;
isLandscape: boolean;
@ -41,7 +42,7 @@ export default class ManageMembers extends PureComponent<ManageMembersProps> {
});
render() {
const {isDirectMessage, canManageUsers, isLandscape, membersCount, separator, theme} = this.props;
const {testID, isDirectMessage, canManageUsers, isLandscape, membersCount, separator, theme} = this.props;
if (isDirectMessage) {
return null;
@ -51,6 +52,7 @@ export default class ManageMembers extends PureComponent<ManageMembersProps> {
<>
{separator && <Separator theme={theme}/>}
<ChannelInfoRow
testID={testID}
action={this.goToChannelMembers}
defaultMessage={canManageUsers ? 'Manage Members' : 'View Members'}
detail={membersCount}

View file

@ -9,6 +9,7 @@ import {t} from '@utils/i18n';
import {preventDoubleTap} from '@utils/tap';
interface MuteProps {
testID?: string;
channelId: string;
isChannelMuted: boolean;
isLandscape: boolean;
@ -17,7 +18,7 @@ interface MuteProps {
userId: string;
}
const Mute = ({channelId, isChannelMuted, isLandscape, updateChannelNotifyProps, userId, theme}: MuteProps) => {
const Mute = ({testID, channelId, isChannelMuted, isLandscape, updateChannelNotifyProps, userId, theme}: MuteProps) => {
const [muted, setMuted] = useState(isChannelMuted);
const handleMuteChannel = preventDoubleTap(() => {
@ -31,6 +32,7 @@ const Mute = ({channelId, isChannelMuted, isLandscape, updateChannelNotifyProps,
return (
<ChannelInfoRow
testID={testID}
action={handleMuteChannel}
defaultMessage='Mute channel'
detail={muted}

View file

@ -11,6 +11,7 @@ import {t} from '@utils/i18n';
import {preventDoubleTap} from '@utils/tap';
interface PinnedProps {
testID?: string;
channelId: string;
pinnedCount: number;
isLandscape: boolean;
@ -38,9 +39,10 @@ export default class Pinned extends PureComponent<PinnedProps> {
});
render() {
const {pinnedCount, isLandscape, theme} = this.props;
const {testID, pinnedCount, isLandscape, theme} = this.props;
return (
<ChannelInfoRow
testID={testID}
action={this.goToPinnedPosts}
defaultMessage='Pinned Messages'
detail={pinnedCount}

View file

@ -332,6 +332,7 @@ LongPost {
shouldRenderReplyButton={false}
showAddReaction={false}
showLongPost={true}
testID="long_post.post.post-id"
/>
</ScrollView>
<View

View file

@ -118,6 +118,7 @@ export default class LongPost extends PureComponent {
isLandscape,
} = this.props;
const style = getStyleSheet(theme);
const testID = `long_post.post.${postId}`;
return (
<SafeAreaView
@ -158,6 +159,7 @@ export default class LongPost extends PureComponent {
</View>
<ScrollView style={style.postList}>
<Post
testID={testID}
postId={postId}
shouldRenderReplyButton={false}
onPress={this.handlePress}

View file

@ -350,7 +350,7 @@ export default class MoreDirectMessages extends PureComponent {
text: formatMessage({id: 'mobile.more_dms.start', defaultMessage: 'Start'}),
showAsAction: 'always',
enabled: startEnabled,
testID: START_BUTTON,
testID: 'more_direct_messages.start.button',
}],
});
};
@ -366,7 +366,7 @@ export default class MoreDirectMessages extends PureComponent {
selectable={true}
selected={selected}
enabled={true}
testID='more_dms.user'
testID='more_direct_messages.user'
/>
);
};
@ -477,7 +477,7 @@ export default class MoreDirectMessages extends PureComponent {
}
return (
<KeyboardLayout testID='direct_channels.screen'>
<KeyboardLayout testID='more_direct_messages.screen'>
<StatusBar/>
<View style={style.searchBar}>
<View style={padding(isLandscape)}>
@ -519,7 +519,7 @@ export default class MoreDirectMessages extends PureComponent {
onLoadMore={this.getProfiles}
onRowPress={this.handleSelectProfile}
renderItem={this.renderItem}
testID='more_dms.list'
testID='more_direct_messages.list'
theme={theme}
/>
</KeyboardLayout>

View file

@ -127,7 +127,7 @@ const Notification = ({componentId, notification}: NotificationProps) => {
style={styles.container}
useNativeDriver={true}
animation={animation}
testID='in_app_notification'
testID='in_app_notification.screen'
>
<View style={styles.flex}>
<TouchableOpacity

View file

@ -47,12 +47,14 @@ exports[`PostOptions should match snapshot, no option for system message to user
<Connect(ReactionPicker)
addReaction={[Function]}
openReactionScreen={[Function]}
testID="post_options.reaction_picker.action"
/>
<PostOption
destructive={false}
icon="reply-outline"
isLandscape={false}
onPress={[Function]}
testID="post.options.reply.action"
text="Reply"
theme={
Object {
@ -88,6 +90,7 @@ exports[`PostOptions should match snapshot, no option for system message to user
icon="mark-as-unread"
isLandscape={false}
onPress={[Function]}
testID="post.options.markUnread.action"
text="Mark as Unread"
theme={
Object {
@ -124,6 +127,7 @@ exports[`PostOptions should match snapshot, no option for system message to user
icon="pin-outline"
isLandscape={false}
onPress={[Function]}
testID="post.options.pin.action"
text="Pin to Channel"
theme={
Object {
@ -160,6 +164,7 @@ exports[`PostOptions should match snapshot, no option for system message to user
icon="pencil-outline"
isLandscape={false}
onPress={[Function]}
testID="post.options.edit.action"
text="Edit"
theme={
Object {
@ -242,12 +247,14 @@ exports[`PostOptions should match snapshot, showing Delete option only for syste
<Connect(ReactionPicker)
addReaction={[Function]}
openReactionScreen={[Function]}
testID="post_options.reaction_picker.action"
/>
<PostOption
destructive={false}
icon="reply-outline"
isLandscape={false}
onPress={[Function]}
testID="post.options.reply.action"
text="Reply"
theme={
Object {
@ -283,6 +290,7 @@ exports[`PostOptions should match snapshot, showing Delete option only for syste
icon="mark-as-unread"
isLandscape={false}
onPress={[Function]}
testID="post.options.markUnread.action"
text="Mark as Unread"
theme={
Object {
@ -319,6 +327,7 @@ exports[`PostOptions should match snapshot, showing Delete option only for syste
icon="pin-outline"
isLandscape={false}
onPress={[Function]}
testID="post.options.pin.action"
text="Pin to Channel"
theme={
Object {
@ -355,6 +364,7 @@ exports[`PostOptions should match snapshot, showing Delete option only for syste
icon="pencil-outline"
isLandscape={false}
onPress={[Function]}
testID="post.options.edit.action"
text="Edit"
theme={
Object {
@ -391,6 +401,7 @@ exports[`PostOptions should match snapshot, showing Delete option only for syste
icon="trash-can-outline"
isLandscape={false}
onPress={[Function]}
testID="post.options.delete.action"
text="Delete"
theme={
Object {
@ -473,12 +484,14 @@ exports[`PostOptions should match snapshot, showing all possible options 1`] = `
<Connect(ReactionPicker)
addReaction={[Function]}
openReactionScreen={[Function]}
testID="post_options.reaction_picker.action"
/>
<PostOption
destructive={false}
icon="reply-outline"
isLandscape={false}
onPress={[Function]}
testID="post.options.reply.action"
text="Reply"
theme={
Object {
@ -514,6 +527,7 @@ exports[`PostOptions should match snapshot, showing all possible options 1`] = `
icon="mark-as-unread"
isLandscape={false}
onPress={[Function]}
testID="post.options.markUnread.action"
text="Mark as Unread"
theme={
Object {
@ -550,6 +564,7 @@ exports[`PostOptions should match snapshot, showing all possible options 1`] = `
icon="pin-outline"
isLandscape={false}
onPress={[Function]}
testID="post.options.pin.action"
text="Pin to Channel"
theme={
Object {
@ -586,6 +601,7 @@ exports[`PostOptions should match snapshot, showing all possible options 1`] = `
icon="pencil-outline"
isLandscape={false}
onPress={[Function]}
testID="post.options.edit.action"
text="Edit"
theme={
Object {
@ -622,6 +638,7 @@ exports[`PostOptions should match snapshot, showing all possible options 1`] = `
icon="trash-can-outline"
isLandscape={false}
onPress={[Function]}
testID="post.options.delete.action"
text="Delete"
theme={
Object {

View file

@ -18,6 +18,7 @@ import {preventDoubleTap} from '@utils/tap';
export default class PostOption extends PureComponent {
static propTypes = {
testID: PropTypes.string,
destructive: PropTypes.bool,
icon: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
@ -31,7 +32,7 @@ export default class PostOption extends PureComponent {
}, 500);
render() {
const {destructive, icon, text, isLandscape, theme} = this.props;
const {testID, destructive, icon, text, isLandscape, theme} = this.props;
const style = getStyleSheet(theme);
const Touchable = Platform.select({
@ -52,7 +53,10 @@ export default class PostOption extends PureComponent {
});
return (
<View style={style.container}>
<View
testID={testID}
style={style.container}
>
<Touchable
onPress={this.handleOnPress}
{...touchableProps}

View file

@ -75,9 +75,11 @@ export default class PostOptions extends PureComponent {
getOption = (key, icon, message, onPress, destructive = false) => {
const {formatMessage} = this.context.intl;
const {isLandscape, theme} = this.props;
const testID = `post.options.${key}.action`;
return (
<PostOption
testID={testID}
key={key}
icon={icon}
text={formatMessage(message)}
@ -222,6 +224,7 @@ export default class PostOptions extends PureComponent {
if (!isSystemMessage(post) && this.props.canMarkAsUnread) {
return (
<PostOption
testID='post.options.markUnread.action'
key='markUnread'
icon='mark-as-unread'
text={formatMessage({id: 'mobile.post_info.mark_unread', defaultMessage: 'Mark as Unread'})}
@ -403,6 +406,7 @@ export default class PostOptions extends PureComponent {
reactionHeight = REACTION_PICKER_HEIGHT;
reactionPicker = (
<ReactionPicker
testID='post_options.reaction_picker.action'
addReaction={this.handleAddReaction}
openReactionScreen={this.handleAddReactionScreen}
/>

View file

@ -45,7 +45,7 @@ describe('PostOptions', () => {
currentUserId: 'user1',
deviceHeight: 600,
hasBeenDeleted: false,
isFlagged: false,
isFlagged: true,
isSystemMessage: false,
managedConfig: {},
post,

View file

@ -7,6 +7,7 @@ exports[`RecentMentions should match snapshot 1`] = `
"flex": 1,
}
}
testID="recent_mentions.screen"
>
<Connect(StatusBar) />
<Connect(ChannelLoader)
@ -22,6 +23,7 @@ exports[`RecentMentions should match snapshot when component waiting for respons
"flex": 1,
}
}
testID="recent_mentions.screen"
>
<Connect(StatusBar) />
<Connect(ChannelLoader)
@ -37,6 +39,7 @@ exports[`RecentMentions should match snapshot when getRecentMentions failed 1`]
"flex": 1,
}
}
testID="recent_mentions.screen"
>
<Connect(StatusBar) />
<Connect(FailedNetworkAction)

View file

@ -222,7 +222,10 @@ export default class RecentMentions extends PureComponent {
}
return (
<View style={style.container}>
<View
testID='recent_mentions.screen'
style={style.container}
>
<StatusBar/>
{component}
</View>

View file

@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SearchResultPost should match snapshot 1`] = `
<Connect(Post)
highlightPinnedOrFlagged={false}
isSearchResult={true}
managedConfig={Object {}}
onHashtagPress={[MockFunction]}
onPermalinkPress={[MockFunction]}
onPress={[MockFunction]}
onReply={[MockFunction]}
postId="post-id"
shouldRenderReplyButton={true}
showAddReaction={false}
showFullDate={false}
skipFlaggedHeader={false}
skipPinnedHeader={false}
testID="search_result_post.post.post-id"
/>
`;

View file

@ -42,8 +42,11 @@ export default class SearchResultPost extends PureComponent {
postComponentProps.skipPinnedHeader = this.props.skipPinnedHeader;
}
const testID = `search_result_post.post.${this.props.postId}`;
return (
<Post
testID={testID}
postId={this.props.postId}
{...postComponentProps}
isSearchResult={true}

View file

@ -0,0 +1,30 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallowWithIntl} from 'test/intl-test-helper';
import SearchResultPost from './search_result_post';
describe('SearchResultPost', () => {
const baseProps = {
goToThread: jest.fn(),
onHashtagPress: jest.fn(),
onPermalinkPress: jest.fn(),
previewPost: jest.fn(),
postId: 'post-id',
isDeleted: false,
highlightPinnedOrFlagged: false,
managedConfig: {},
showFullDate: false,
skipFlaggedHeader: false,
skipPinnedHeader: false,
};
test('should match snapshot', async () => {
const wrapper = shallowWithIntl(<SearchResultPost {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
});

View file

@ -211,6 +211,7 @@ class Settings extends PureComponent {
isLandscape={isLandscape}
/>
<SettingsItem
testID='general_settings.display.action'
defaultMessage='Display'
i18nId={t('user.settings.modal.display')}
iconName='layers-outline'
@ -223,6 +224,7 @@ class Settings extends PureComponent {
{showTeams &&
<React.Fragment>
<SettingsItem
testID='general_settings.select_team.action'
defaultMessage='Open teams you can join'
i18nId={t('mobile.select_team.join_open')}
iconName='menu'
@ -235,6 +237,7 @@ class Settings extends PureComponent {
</React.Fragment>
}
<SettingsItem
testID='general_settings.advanced.action'
defaultMessage='Advanced Settings'
i18nId={t('mobile.advanced_settings.title')}
iconName='tune'
@ -247,6 +250,7 @@ class Settings extends PureComponent {
{LocalConfig.EnableMobileClientUpgrade && LocalConfig.EnableMobileClientUpgradeUserSetting &&
<React.Fragment>
<SettingsItem
testID='general_settings.check_for_upgrade.action'
defaultMessage='Check for Upgrade'
i18nId={t('mobile.settings.modal.check_for_upgrade')}
iconName='update'
@ -259,6 +263,7 @@ class Settings extends PureComponent {
</React.Fragment>
}
<SettingsItem
testID='general_settings.about.action'
defaultMessage='About {appTitle}'
messageValues={{appTitle: config.SiteName || 'Mattermost'}}
i18nId={t('about.title')}
@ -273,6 +278,7 @@ class Settings extends PureComponent {
{showHelp &&
<React.Fragment>
<SettingsItem
testID='general_settings.help.action'
defaultMessage='Help'
i18nId={t('mobile.help.title')}
onPress={this.openHelp}
@ -285,6 +291,7 @@ class Settings extends PureComponent {
</React.Fragment>
}
<SettingsItem
testID='general_settings.report.action'
defaultMessage='Report a Problem'
i18nId={t('sidebar_right_menu.report')}
onPress={this.openErrorEmail}

View file

@ -16,6 +16,34 @@ import {getResponseFromError} from './common';
// - return value defined by `@return`
// ****************************************************************
/**
* Create a new post in a channel. To create the post as a comment on another post, provide root_id.
* See https://api.mattermost.com/#tag/posts/paths/~1posts/post
* @param {string} option.channelId - The channel ID to post in
* @param {string} option.message - The message contents, can be formatted with Markdown
* @param {string} option.rootId - The post ID to comment on
* @param {Object} option.props - A general object property bag to attach to the post
* @param {Date} option.createAt - The date the post is created at
* @return {Object} returns {post} on success or {error, status} on error
*/
export const apiCreatePost = async ({channelId, message, rootId, props = {}, createAt = 0}) => {
try {
const payload = {
channel_id: channelId,
message,
parent_id: rootId,
root_id: rootId,
props,
create_at: createAt,
};
const response = await client.post('/api/v4/posts', payload);
return {post: response.data};
} catch (err) {
return getResponseFromError(err);
}
};
/**
* Get posts for a channel.
* See https://api.mattermost.com/#tag/posts/paths/~1channels~1{channel_id}~1posts/get
@ -47,6 +75,7 @@ export const apiGetLastPostInChannel = async (channelId) => {
};
export const Post = {
apiCreatePost,
apiGetLastPostInChannel,
apiGetPostsInChannel,
};

View file

@ -6,7 +6,10 @@ import CameraQuickAction from './camera_quick_action';
import FileQuickAction from './file_quick_action';
import ImageQuickAction from './image_quick_action';
import MainSidebar from './main_sidebar';
import Post from './post';
import PostDraft from './post_draft';
import PostOptions from './post_options';
import SendButton from './send_button';
import SettingsSidebar from './settings_sidebar';
export {
@ -15,6 +18,9 @@ export {
FileQuickAction,
ImageQuickAction,
MainSidebar,
Post,
PostDraft,
PostOptions,
SendButton,
SettingsSidebar,
};

View file

@ -6,15 +6,21 @@ class MainSidebar {
mainSidebar: 'main.sidebar',
channelItemDisplayName: 'channel_item.display_name',
channelsList: 'channels.list',
addChannel: 'action_button_sidebar.channels',
addDirectChannel: 'action_button_sidebar.direct',
openMoreChannelsButton: 'action_button_sidebar.channels',
openCreatePrivateChannelButton: 'action_button_sidebar.pg',
openMoreDirectMessagesButton: 'action_button_sidebar.direct',
}
mainSidebar = element(by.id(this.testID.mainSidebar));
channelItemDisplayName = element(by.id(this.testID.channelItemDisplayName));
channelsList = element(by.id(this.testID.channelsList));
addChannel = element(by.id(this.testID.addChannel));
addDirectChannel = element(by.id(this.testID.addDirectChannel));
openMoreChannelsButton = element(by.id(this.testID.openMoreChannelsButton));
openCreatePrivateChannelButton = element(by.id(this.testID.openCreatePrivateChannelButton));
openMoreDirectMessagesButton = element(by.id(this.testID.openMoreDirectMessagesButton));
getChannelByDisplayName = (displayName) => {
return element(by.text(displayName).withAncestor(by.id(this.testID.channelsList)));
}
toBeVisible = async () => {
await expect(this.mainSidebar).toBeVisible();
@ -22,10 +28,6 @@ class MainSidebar {
return this.mainSidebar;
}
getChannelByDisplayName = (displayName) => {
return element(by.text(displayName).withAncestor(by.id(this.testID.channelsList)));
}
hasChannelAtIndex = async (index, channelDisplayName) => {
await expect(
element(by.id(this.testID.channelItemDisplayName).withAncestor(by.id(this.testID.channelsList))).atIndex(index),

View file

@ -0,0 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
class Post {
testID = {
postPrefix: 'post.',
}
getPost = (postTypePrefix, postId, text) => {
if (text) {
return element(by.id(`${postTypePrefix}${this.testID.postPrefix}${postId}`).withDescendant(by.text(text)));
}
return element(by.id(`${postTypePrefix}${this.testID.postPrefix}${postId}`));
}
}
const post = new Post();
export default post;

View file

@ -0,0 +1,34 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
class PostDraft {
testID = {
postDraft: 'post_draft',
postDraftArchived: 'post_draft.archived',
postDraftReadOnly: 'post_draft.archived',
postInput: 'post_draft.post.input',
}
postDraft = element(by.id(this.testID.postDraft));
postDraftArchived = element(by.id(this.testID.postDraftArchived));
postDraftReadOnly = element(by.id(this.testID.postDraftReadOnly));
postInput = element(by.id(this.testID.postInput));
toBeVisible = async (options = {archived: false, readOnly: false}) => {
if (options.archived) {
await expect(this.postDraftArchived).toBeVisible();
return this.postDraftArchived;
}
if (options.readOnly) {
await expect(this.postDraftReadOnly).toBeVisible();
return this.postDraftReadOnly;
}
await expect(this.postDraft).toBeVisible();
return this.postDraft;
}
}
const postDraft = new PostDraft();
export default postDraft;

View file

@ -4,17 +4,45 @@
class PostOptions {
testID = {
postOptions: 'post.options',
openReactionPicker: 'open.reaction_picker',
reactionPickerAction: 'post.options.reaction_picker.action',
replyAction: 'post.options.reply.action',
permalinkAction: 'post.options.permalink.action',
copyAction: 'post.options.copy.action',
deleteAction: 'post.options.delete.action',
editAction: 'post.options.edit.action',
saveAction: 'post.options.flagged.action',
unsaveAction: 'post.options.unflag.action',
pinAction: 'post.options.pin.action',
unpinAction: 'post.options.unpin.action',
markUnreadAction: 'post.options.markUnread.action',
openAddReactionButton: 'open.add_reaction.button',
slideUpPanel: 'slide_up_panel',
}
postOptions = element(by.id(this.testID.postOptions));
openReactionPicker = element(by.id(this.testID.openReactionPicker));
reactionPickerAction = element(by.id(this.testID.reactionPickerAction));
replyAction = element(by.id(this.testID.replyAction));
permalinkAction = element(by.id(this.testID.permalinkAction));
deleteAction = element(by.id(this.testID.deleteAction));
editAction = element(by.id(this.testID.editAction));
saveAction = element(by.id(this.testID.saveAction));
unsaveAction = element(by.id(this.testID.unsaveAction));
pinAction = element(by.id(this.testID.pinAction));
unpinAction = element(by.id(this.testID.unpinAction));
markUnreadAction = element(by.id(this.testID.markUnreadAction));
openAddReactionButton = element(by.id(this.testID.openAddReactionButton));
slideUpPanel = element(by.id(this.testID.slideUpPanel));
toBeVisible = async () => {
await expect(this.postOptions).toBeVisible();
return postOptions;
}
close = async () => {
await this.postOptions.tap();
await expect(this.postOptions).not.toBeVisible();
}
}
const postOptions = new PostOptions();

View file

@ -0,0 +1,25 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
class SendButton {
testID = {
sendButton: 'post_draft.send.button',
sendButtonDisabled: 'post_draft.send.button.disabled',
}
sendButton = element(by.id(this.testID.sendButton));
sendButtonDisabled = element(by.id(this.testID.sendButtonDisabled));
toBeVisible = async (options = {disabled: false}) => {
if (options.disabled) {
await expect(this.sendButtonDisabled).toBeVisible();
return this.sendButtonDisabled;
}
await expect(this.sendButton).toBeVisible();
return this.sendButton;
}
}
const sendButton = new SendButton();
export default sendButton;

View file

@ -4,11 +4,21 @@
class SettingsSidebar {
testID = {
settingsSidebar: 'settings.sidebar',
userInfoAction: 'settings.sidebar.user_info.action',
statusAction: 'settings.sidebar.status.action',
recentMentionsAction: 'settings.sidebar.recent_mentions.action',
savedMessagesAction: 'settings.sidebar.saved_messages.action',
editProfileAction: 'settings.sidebar.edit_profile.action',
settingsAction: 'settings.sidebar.settings.action',
logoutAction: 'settings.sidebar.logout.action',
}
settingsSidebar = element(by.id(this.testID.settingsSidebar));
userInfoAction = element(by.id(this.testID.userInfoAction));
statusAction = element(by.id(this.testID.statusAction));
recentMentionsAction = element(by.id(this.testID.recentMentionsAction));
savedMessagesAction = element(by.id(this.testID.savedMessagesAction));
editProfileAction = element(by.id(this.testID.editProfileAction));
settingsAction = element(by.id(this.testID.settingsAction));
logoutAction = element(by.id(this.testID.logoutAction));

View file

@ -18,10 +18,9 @@ class AddReactionScreen {
return this.addReactionScreen;
}
open = async (message) => {
await element(by.text(message)).longPress();
await PostOptions.toBeVisible();
await PostOptions.openReactionPicker.tap();
open = async () => {
// # Open add reaction screen
await PostOptions.openAddReactionButton.tap();
return this.toBeVisible();
}

View file

@ -1,8 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {SettingsSidebar} from '@support/ui/component';
import {LoginScreen, SelectServerScreen} from '@support/ui/screen';
import {
CameraQuickAction,
FileQuickAction,
ImageQuickAction,
MainSidebar,
PostDraft,
PostOptions,
SendButton,
SettingsSidebar,
} from '@support/ui/component';
import {
LoginScreen,
LongPostScreen,
PostListScreen,
SelectServerScreen,
} from '@support/ui/screen';
class ChannelScreen {
testID = {
@ -12,9 +26,6 @@ class ChannelScreen {
channelNavBarTitle: 'channel.nav_bar.title',
channelSearchButton: 'channel.search.button',
channelTitleButton: 'channel.title.button',
disabledSendButton: 'disabled_send.button',
postInput: 'post.input',
sendButton: 'send.button',
settingsSidebarDrawerButton: 'settings_sidebar_drawer.button',
}
@ -24,11 +35,30 @@ class ChannelScreen {
channelNavBarTitle = element(by.id(this.testID.channelNavBarTitle));
channelSearchButton = element(by.id(this.testID.channelSearchButton));
channelTitleButton = element(by.id(this.testID.channelTitleButton));
disabledSendButton = element(by.id(this.testID.disabledSendButton));
postInput = element(by.id(this.testID.postInput));
sendButton = element(by.id(this.testID.sendButton));
settingsSidebarDrawerButton = element(by.id(this.testID.settingsSidebarDrawerButton));
// convenience props
cameraQuickAction = CameraQuickAction.cameraQuickAction;
cameraQuickActionDisabled = CameraQuickAction.cameraQuickActionDisabled;
imageQuickAction = ImageQuickAction.imageQuickAction;
imageQuickActionDisabled = ImageQuickAction.imageQuickActionDisabled;
fileQuickAction = FileQuickAction.fileQuickAction;
fileQuickActionDisabled = FileQuickAction.fileQuickActionDisabled;
postDraft = PostDraft.postDraft;
postDraftArchived = PostDraft.postDraftArchived;
postDraftReadOnly = PostDraft.postDraftReadOnly;
postInput = PostDraft.postInput;
sendButton = SendButton.sendButton;
sendButtonDisabled = SendButton.sendButtonDisabled;
getLongPostPostItem = (postId, text) => {
return LongPostScreen.getPost(postId, text);
}
getPostListPostItem = (postId, text) => {
return PostListScreen.getPost(postId, text);
}
toBeVisible = async () => {
await expect(this.channelScreen).toBeVisible();
@ -36,6 +66,7 @@ class ChannelScreen {
}
open = async (user) => {
// # Open channel screen
await SelectServerScreen.connectToServer();
await LoginScreen.login(user);
@ -43,10 +74,38 @@ class ChannelScreen {
}
logout = async () => {
await this.settingsSidebarDrawerButton.tap();
await this.openSettingsSidebar();
await SettingsSidebar.logoutAction.tap();
await SelectServerScreen.toBeVisible();
}
openMainSidebar = async () => {
// # Open main sidebar
await this.mainSidebarDrawerButton.tap();
await MainSidebar.toBeVisible();
}
openSettingsSidebar = async () => {
// # Open settings sidebar
await this.settingsSidebarDrawerButton.tap();
await SettingsSidebar.toBeVisible();
}
openPostOptionsFor = async (postId, text) => {
const post = await this.getPostListPostItem(postId, text);
await expect(post).toBeVisible();
// # Open post options
await post.longPress();
await PostOptions.toBeVisible();
}
tapSendButton = async () => {
// # Tap send button
await this.sendButton.tap();
await expect(this.sendButton).not.toExist();
await expect(this.sendButtonDisabled).toBeVisible();
}
}
const channelScreen = new ChannelScreen();

View file

@ -9,14 +9,28 @@ class ChannelInfoScreen {
channelInfoScreen: 'channel_info.screen',
closeChannelInfoButton: 'close.channel_info.button',
channelIconGMMemberCount: 'channel_icon.gm_member_count',
favoritePreferenceAction: 'channel_info.favorite.action',
mutePreferenceAction: 'channel_info.mute.action',
ignoreMentionsPreferenceAction: 'channel_info.ignore_mentions.action',
notificationPreferenceAction: 'channel_info.notification_preference.action',
pinnedAction: 'channel_info.pinned.action',
manageMembersAction: 'channel_info.manage_members.action',
addMembersAction: 'channel_info.add_members.action',
convertPrivateAction: 'channel_info.convert_private.action',
editChannelAction: 'channel_info.edit_channel.action',
}
channelInfoScreen = element(by.id(this.testID.channelInfoScreen));
closeChannelInfoButton = element(by.id(this.testID.closeChannelInfoButton));
channelIconGMMemberCount = element(by.id(this.testID.channelIconGMMemberCount));
favoritePreferenceAction = element(by.id(this.testID.favoritePreferenceAction));
mutePreferenceAction = element(by.id(this.testID.mutePreferenceAction));
ignoreMentionsPreferenceAction = element(by.id(this.testID.ignoreMentionsPreferenceAction));
notificationPreferenceAction = element(by.id(this.testID.notificationPreferenceAction));
pinnedAction = element(by.id(this.testID.pinnedAction));
manageMembersAction = element(by.id(this.testID.manageMembersAction));
addMembersAction = element(by.id(this.testID.addMembersAction));
convertPrivateAction = element(by.id(this.testID.convertPrivateAction));
editChannelAction = element(by.id(this.testID.editChannelAction));
toBeVisible = async () => {

View file

@ -1,8 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChannelInfoScreen} from '@support/ui/screen';
class CreateChannelScreen {
testID = {
createChannelScreen: 'create_channel.screen',
@ -26,16 +24,6 @@ class CreateChannelScreen {
return this.createChannelScreen;
}
open = async () => {
// # Open channel info screen
await ChannelInfoScreen.open();
// # Tap on Edit Channel
await ChannelInfoScreen.editChannelAction.tap();
return this.toBeVisible();
}
back = async () => {
await this.backButton.tap();
await expect(this.createChannelScreen).not.toBeVisible();

View file

@ -1,35 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChannelScreen} from '@support/ui/component';
import {MainSidebar} from '@support/ui/screen';
class DirectChannelsScreen {
testID = {
directChannelsScreen: 'direct_channels.screen',
startButton: 'start-conversation',
}
directChannelsScreen = element(by.id(this.testID.directChannelsScreen));
startButton = element(by.id(this.testID.startButton));
getUserAtIndex = (index) => {
return element(by.id('more_dms.user').withAncestor(by.id('more_dms.list'))).atIndex(index);
}
toBeVisible = async () => {
await expect(this.directChannelsScreen).toBeVisible();
return this.directChannelsScreen;
}
open = async () => {
await ChannelScreen.mainSidebarDrawerButton.tap();
await MainSidebar.addDirectChannel.tap();
return this.toBeVisible();
}
}
const directChannelsScreen = new DirectChannelsScreen();
export default directChannelsScreen;

View file

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChannelScreen} from '@support/ui/screen';
import {ChannelInfoScreen} from '@support/ui/screen';
class EditChannelScreen {
testID = {
@ -21,9 +21,8 @@ class EditChannelScreen {
}
open = async () => {
// # Open channel menu then tap 'Edit Channel'
await ChannelScreen.channelTitleButton.tap();
await element(by.text('Edit Channel')).tap();
// # Open edit channel screen
await ChannelInfoScreen.editChannelAction.tap();
return this.toBeVisible();
}

View file

@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {PostOptions} from '@support/ui/component';
import {isAndroid} from '@support/utils';
class EditPostScreen {
@ -10,26 +11,30 @@ class EditPostScreen {
editPostClose: 'edit_post.close',
}
editPostScreen = element(by.id(this.testID.editPostScreen));
editPostInput = element(by.id(this.testID.editPostInput));
editPostClose = element(by.id(this.testID.editPostClose));
toBeVisible = async () => {
await expect(this.editPostScreen).toBeVisible();
if (isAndroid()) {
await expect(this.editPostScreen).toBeVisible();
} else {
await expect(this.editPostScreen).toExist();
}
return this.editPostScreen;
}
open = async (message) => {
// # Open edit post screen
await element(by.text(message)).longPress();
open = async () => {
// # Swipe up panel on Android
if (isAndroid()) {
const slide = element(by.id('slide_up_panel'));
await slide.swipe('up');
await PostOptions.slideUpPanel.swipe('up');
}
await element(by.text('Edit')).tap();
// # Open edit post screen
await PostOptions.editAction.tap();
return this.toBeVisible();
}
back = async () => {

View file

@ -8,11 +8,25 @@ class GeneralSettingsScreen {
generalSettingsScreen: 'general_settings.screen',
closeSettingsButton: 'close.settings.button',
notificationsAction: 'general_settings.notifications.action',
displayAction: 'general_settings.display.action',
selectTeamAction: 'general_settings.select_team.action',
advancedAction: 'general_settings.advanced.action',
checkForUpgradeAction: 'general_settings.checkForUpgrade.action',
aboutAction: 'general_settings.about.action',
helpAction: 'general_settings.help.action',
reportAction: 'general_settings.report.action',
}
generalSettingsScreen = element(by.id(this.testID.generalSettingsScreen));
closeSettingsButton = element(by.id(this.testID.closeSettingsButton));
notificationsAction = element(by.id(this.testID.notificationsAction));
displayAction = element(by.id(this.testID.displayAction));
selectTeamAction = element(by.id(this.testID.selectTeamAction));
advancedAction = element(by.id(this.testID.advancedAction));
checkForUpgradeAction = element(by.id(this.testID.checkForUpgradeAction));
aboutAction = element(by.id(this.testID.aboutAction));
helpAction = element(by.id(this.testID.helpAction));
reportAction = element(by.id(this.testID.reportAction));
toBeVisible = async () => {
await expect(this.generalSettingsScreen).toBeVisible();

View file

@ -6,15 +6,19 @@ import ChannelInfoScreen from './channel_info';
import ChannelNotificationPreferenceScreen from './channel_notification_preference';
import ChannelScreen from './channel';
import CreateChannelScreen from './create_channel';
import DirectChannelsScreen from './direct_channels';
import EditChannelScreen from './edit_channel';
import EditPostScreen from './edit_post';
import GeneralSettingsScreen from './general_settings';
import LoginScreen from './login';
import LongPostScreen from './long_post';
import MoreChannelsScreen from './more_channels';
import MoreDirectMessagesScreen from './more_direct_messages';
import NotificationScreen from './notification';
import NotificationSettingsMobileScreen from './notification_settings_mobile';
import NotificationSettingsScreen from './notification_settings';
import PostListScreen from './post_list';
import RecentMentionsScreen from './recent_mentions';
import SearchResultPostScreen from './search_result_post';
import SearchScreen from './search';
import SelectServerScreen from './select_server';
@ -24,15 +28,19 @@ export {
ChannelNotificationPreferenceScreen,
ChannelScreen,
CreateChannelScreen,
DirectChannelsScreen,
EditChannelScreen,
EditPostScreen,
GeneralSettingsScreen,
LoginScreen,
LongPostScreen,
MoreChannelsScreen,
MoreDirectMessagesScreen,
NotificationScreen,
NotificationSettingsMobileScreen,
NotificationSettingsScreen,
PostListScreen,
RecentMentionsScreen,
SearchResultPostScreen,
SearchScreen,
SelectServerScreen,
};

View file

@ -25,6 +25,7 @@ class LoginScreen {
}
open = async () => {
// # Open login screen
await SelectServerScreen.connectToServer();
return this.toBeVisible();

View file

@ -0,0 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Post} from '@support/ui/component';
class LongPostScreen {
testID = {
longPostScreenPrefix: 'long_post.',
}
getPost = (postId, text) => {
return Post.getPost(this.testID.longPostScreenPrefix, postId, text);
}
}
const longPostScreen = new LongPostScreen();
export default longPostScreen;

View file

@ -1,8 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChannelScreen} from '@support/ui/component';
import {MainSidebar} from '@support/ui/screen';
import {MainSidebar} from '@support/ui/component';
class MoreChannelsScreen {
testID = {
@ -20,8 +19,8 @@ class MoreChannelsScreen {
}
open = async () => {
await ChannelScreen.mainSidebarDrawerButton.tap();
await MainSidebar.addChannel.tap();
// # Open more channels screen
await MainSidebar.openMoreChannelsButton.tap();
return this.toBeVisible();
}

View file

@ -0,0 +1,36 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {MainSidebar} from '@support/ui/component';
class MoreDirectMessagesScreen {
testID = {
moreDirectMessagesScreen: 'more_direct_messages.screen',
startButton: 'more_direct_messages.start.button',
list: 'more_direct_messages.list',
user: 'more_direct_messages.user',
}
moreDirectMessagesScreen = element(by.id(this.testID.moreDirectMessagesScreen));
startButton = element(by.id(this.testID.startButton));
getUserAtIndex = (index) => {
return element(by.id(this.testID.user).withAncestor(by.id(this.testID.list))).atIndex(index);
}
toBeVisible = async () => {
await expect(this.moreDirectMessagesScreen).toBeVisible();
return this.moreDirectMessagesScreen;
}
open = async () => {
// # Open more direct messages screen
await MainSidebar.openMoreDirectMessagesButton.tap();
return this.toBeVisible();
}
}
const moreDirectMessagesScreen = new MoreDirectMessagesScreen();
export default moreDirectMessagesScreen;

View file

@ -3,21 +3,21 @@
class NotificationScreen {
testID = {
inAppNotification: 'in_app_notification',
inAppNotificationScreen: 'in_app_notification.screen',
inAppNotificationIcon: 'in_app_notification.icon',
inAppNotificationTitle: 'in_app_notification.title',
inAppNotificationMessage: 'in_app_notification.message',
}
inAppNotification = element(by.id(this.testID.inAppNotification));
inAppNotificationScreen = element(by.id(this.testID.inAppNotificationScreen));
inAppNotificationIcon = element(by.id(this.testID.inAppNotificationIcon));
inAppNotificationTitle = element(by.id(this.testID.inAppNotificationTitle));
inAppNotificationMessage = element(by.id(this.testID.inAppNotificationMessage));
toBeVisible = async () => {
await expect(this.inAppNotification).toBeVisible();
await expect(this.inAppNotificationScreen).toBeVisible();
return this.inAppNotification;
return this.inAppNotificationScreen;
}
}

View file

@ -22,7 +22,7 @@ class NotificationSettingsScreen {
open = async () => {
// # Open notification settings screen
await GeneralSettingsScreen.notificationAction.tap();
await GeneralSettingsScreen.notificationsAction.tap();
return this.toBeVisible();
}

View file

@ -30,6 +30,19 @@ class NotificationSettingsMobileScreen {
pushModalCancelButton = element(by.id(this.testID.pushModalCancelButton));
pushModalSaveButton = element(by.id(this.testID.pushModalSaveButton));
getPushActionFor = (pushKey) => {
switch (pushKey) {
case 'all':
return this.allAction;
case 'mentions':
return this.mentionsAction;
case 'never':
return this.neverAction;
default:
throw new Error('Not a valid push option: ' + pushKey);
}
}
toBeVisible = async () => {
await expect(this.notificationSettingsMobileScreen).toBeVisible();
@ -47,19 +60,6 @@ class NotificationSettingsMobileScreen {
await this.backButton.tap();
await expect(this.notificationSettingsMobileScreen).not.toBeVisible();
}
getPushActionFor(pushKey) {
switch (pushKey) {
case 'all':
return this.allAction;
case 'mentions':
return this.mentionsAction;
case 'never':
return this.neverAction;
default:
throw new Error('Not a valid push option: ' + pushKey);
}
}
}
const notificationSettingsMobileScreen = new NotificationSettingsMobileScreen();

View file

@ -0,0 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Post} from '@support/ui/component';
class PostListScreen {
testID = {
postListScreenPrefix: 'post_list.',
}
getPost = (postId, text) => {
return Post.getPost(this.testID.postListScreenPrefix, postId, text);
}
}
const postListScreen = new PostListScreen();
export default postListScreen;

View file

@ -0,0 +1,52 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {
PostOptions,
SettingsSidebar,
} from '@support/ui/component';
import {SearchResultPostScreen} from '@support/ui/screen';
class RecentMentionsScreen {
testID = {
recentMentionsScreen: 'recent_mentions.screen',
closeSettingsButton: 'close.settings.button',
}
recentMentionsScreen = element(by.id(this.testID.recentMentionsScreen));
closeSettingsButton = element(by.id(this.testID.closeSettingsButton));
getSearchResultPostItem = (postId, text) => {
return SearchResultPostScreen.getPost(postId, text);
}
toBeVisible = async () => {
await expect(this.recentMentionsScreen).toBeVisible();
return this.recentMentionsScreen;
}
open = async () => {
// # Open recent mentions screen
await SettingsSidebar.recentMentionsAction.tap();
return this.toBeVisible();
}
close = async () => {
await this.closeSettingsButton.tap();
await expect(this.recentMentionsScreen).not.toBeVisible();
}
openPostOptionsFor = async (postId, text) => {
const post = await this.getSearchResultPostItem(postId, text);
await expect(post).toBeVisible();
// # Open post options
await post.longPress();
await PostOptions.toBeVisible();
}
}
const recentMentionsScreen = new RecentMentionsScreen();
export default recentMentionsScreen;

View file

@ -26,6 +26,7 @@ class SearchScreen {
}
open = async () => {
// # Open search screen
await ChannelScreen.channelSearchButton.tap();
return this.toBeVisible();

View file

@ -0,0 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Post} from '@support/ui/component';
class SearchResultPostScreen {
testID = {
searchResultPostScreenPrefix: 'search_result_post.',
}
getPost = (postId, text) => {
return Post.getPost(this.testID.searchResultPostScreenPrefix, postId, text);
}
}
const searchResultPostScreen = new SearchResultPostScreen();
export default searchResultPostScreen;

View file

@ -20,6 +20,8 @@ describe('Autocomplete', () => {
beforeAll(async () => {
({user} = await Setup.apiInit());
userAtMentionAutocomplete = Autocomplete.getAtMentionItem(user.id);
// # Open channel screen
await ChannelScreen.open(user);
});
@ -171,21 +173,21 @@ describe('Autocomplete', () => {
it('MM-T3409_11 should be able to select at mention multiple times', async () => {
// # Type "@" to activate at mention autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await expect(atMentionSuggestionList).not.toExist();
await postInput.typeText('@');
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await expect(atMentionSuggestionList).toExist();
// # Type username
await postInput.typeText(user.username);
// # Tap user
await element(by.id(`autocomplete.at_mention.item.${user.id}`)).tap();
await userAtMentionAutocomplete.tap();
// * expect mention autocomplete to dissappear
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await expect(atMentionSuggestionList).not.toExist();
// # Type "@" again to re-activate mention autocomplete
await postInput.typeText('@');
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await expect(atMentionSuggestionList).toExist();
});
});

View file

@ -18,6 +18,8 @@ import {Setup} from '@support/server_api';
describe('Autocomplete', () => {
beforeAll(async () => {
const {user} = await Setup.apiInit();
// # Open channel screen
await ChannelScreen.open(user);
});
@ -26,7 +28,8 @@ describe('Autocomplete', () => {
});
it('MM-T3390 should render autocomplete in channel header edit screen', async () => {
// # Open edit channel modal
// # Open edit channel screen
await ChannelInfoScreen.open();
await EditChannelScreen.open();
// # Activate at_mention autocomplete

View file

@ -8,13 +8,25 @@
// *******************************************************************
import {Autocomplete} from '@support/ui/component';
import {ChannelScreen, EditPostScreen} from '@support/ui/screen';
import {isAndroid, timeouts, wait} from '@support/utils';
import {Setup} from '@support/server_api';
import {
ChannelScreen,
EditPostScreen,
} from '@support/ui/screen';
import {
Channel,
Post,
Setup,
} from '@support/server_api';
describe('Autocomplete', () => {
let testChannel;
beforeAll(async () => {
const {user} = await Setup.apiInit();
const {team, user} = await Setup.apiInit();
const {channel} = await Channel.apiGetChannelByName(team.name, 'town-square');
testChannel = channel;
// # Open channel screen
await ChannelScreen.open(user);
});
@ -23,23 +35,20 @@ describe('Autocomplete', () => {
});
it('MM-T3391 should render autocomplete in post edit screen', async () => {
const message = Date.now().toString();
const {postInput, sendButton} = ChannelScreen;
const testMessage = Date.now().toString();
const {postInput} = ChannelScreen;
// # Type a message
await postInput.tap();
await postInput.typeText(message);
await postInput.typeText(testMessage);
// # Tap the send button
await sendButton.tap();
// # Tap send button
await ChannelScreen.tapSendButton();
// # Explicitly wait on Android before verifying error message
if (isAndroid()) {
await wait(timeouts.ONE_SEC);
}
// # Open edit screen
await EditPostScreen.open(message);
// # Open edit post screen
const {post} = await Post.apiGetLastPostInChannel(testChannel.id);
await ChannelScreen.openPostOptionsFor(post.id, testMessage);
await EditPostScreen.open();
const {atMentionSuggestionList} = Autocomplete;
const {editPostInput, editPostClose} = EditPostScreen;

View file

@ -22,6 +22,8 @@ describe('Autocomplete', () => {
beforeAll(async () => {
const {user} = await Setup.apiInit();
// # Open channel screen
await ChannelScreen.open(user);
});

View file

@ -20,9 +20,9 @@ describe('Autocomplete', () => {
beforeAll(async () => {
const {user} = await Setup.apiInit();
await ChannelScreen.open(user);
// # Open search screen
await ChannelScreen.open(user);
await SearchScreen.open();
});

View file

@ -7,11 +7,10 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {MainSidebar} from '@support/ui/component';
import {
ChannelInfoScreen,
ChannelScreen,
DirectChannelsScreen,
MoreDirectMessagesScreen,
} from '@support/ui/screen';
import {timeouts, wait} from '@support/utils';
import {Setup} from '@support/server_api';
@ -19,6 +18,8 @@ import {Setup} from '@support/server_api';
describe('Channel Info Header', () => {
beforeAll(async () => {
const {user} = await Setup.apiInit();
// # Open channel screen
await ChannelScreen.open(user);
});
@ -27,13 +28,9 @@ describe('Channel Info Header', () => {
});
it('MM-T3406 should render correct GM member count in channel info header', async () => {
// # Open main sidebar
await ChannelScreen.mainSidebarDrawerButton.tap();
await MainSidebar.toBeVisible();
// # Open Direct Channels screen
await MainSidebar.addDirectChannel.tap();
await DirectChannelsScreen.toBeVisible();
// # Open more direct messages screen
await ChannelScreen.openMainSidebar();
await MoreDirectMessagesScreen.open();
// # Wait for some profiles to load
await wait(timeouts.ONE_SEC);
@ -41,7 +38,7 @@ describe('Channel Info Header', () => {
const {
getUserAtIndex,
startButton,
} = DirectChannelsScreen;
} = MoreDirectMessagesScreen;
// # Select 3 profiles
await getUserAtIndex(0).tap();

View file

@ -9,7 +9,6 @@
import jestExpect from 'expect';
import {MainSidebar} from '@support/ui/component';
import {
ChannelScreen,
ChannelInfoScreen,
@ -22,6 +21,7 @@ describe('Channels', () => {
beforeAll(async () => {
const {user} = await Setup.apiInit();
// # Open channel screen
await ChannelScreen.open(user);
});
@ -30,14 +30,11 @@ describe('Channels', () => {
});
it('MM-T3201 Create public channel', async () => {
// # Go to channel sidebar list
await ChannelScreen.mainSidebarDrawerButton.tap();
// # Tap on + public channels
await MainSidebar.addChannel.tap();
// # Open more channels screen
await ChannelScreen.openMainSidebar();
await MoreChannelsScreen.open();
// * Expect a list of public channels, initially empty
await MoreChannelsScreen.toBeVisible();
await expect(element(by.text('No more channels to join'))).toBeVisible();
// # Tap to create new channel

View file

@ -7,11 +7,6 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {
CameraQuickAction,
FileQuickAction,
ImageQuickAction,
} from '@support/ui/component';
import {ChannelScreen} from '@support/ui/screen';
import {
Setup,
@ -35,9 +30,15 @@ describe('Disable Upload', () => {
});
it('MM-T3453 should disable file, image, camera icons when mobile file upload is disabled', async () => {
const {
cameraQuickActionDisabled,
fileQuickActionDisabled,
imageQuickActionDisabled,
} = ChannelScreen;
// * Verify disabled file, image, camera icons are visible
await expect(CameraQuickAction.cameraQuickActionDisabled).toBeVisible();
await expect(FileQuickAction.fileQuickActionDisabled).toBeVisible();
await expect(ImageQuickAction.imageQuickActionDisabled).toBeVisible();
await expect(cameraQuickActionDisabled).toBeVisible();
await expect(fileQuickActionDisabled).toBeVisible();
await expect(imageQuickActionDisabled).toBeVisible();
});
});

View file

@ -7,11 +7,6 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {
CameraQuickAction,
FileQuickAction,
ImageQuickAction,
} from '@support/ui/component';
import {ChannelScreen} from '@support/ui/screen';
import {
Setup,
@ -35,9 +30,15 @@ describe('Enable Upload', () => {
});
it('MM-T3483 should enable file, image, camera icons when mobile file upload is enabled', async () => {
const {
cameraQuickAction,
fileQuickAction,
imageQuickAction,
} = ChannelScreen;
// * Verify enabled file, image, camera icons are visible
await expect(CameraQuickAction.cameraQuickAction).toBeVisible();
await expect(FileQuickAction.fileQuickAction).toBeVisible();
await expect(ImageQuickAction.imageQuickAction).toBeVisible();
await expect(cameraQuickAction).toBeVisible();
await expect(fileQuickAction).toBeVisible();
await expect(imageQuickAction).toBeVisible();
});
});

View file

@ -19,6 +19,7 @@ describe('Select channel', () => {
const {user, channel} = await Setup.apiInit();
newChannel = channel;
// # Open channel screen
await ChannelScreen.open(user);
});
@ -27,13 +28,10 @@ describe('Select channel', () => {
});
it('MM-T3412 should close the sidebar menu when selecting the same channel', async () => {
const {mainSidebarDrawerButton, channelNavBarTitle} = ChannelScreen;
const {channelNavBarTitle} = ChannelScreen;
// # Open main sidebar (with at least one unread channel)
await mainSidebarDrawerButton.tap();
// * Main sidebar should be visible
await MainSidebar.toBeVisible();
await ChannelScreen.openMainSidebar();
// # Tap a channel to view it
const channelItem = MainSidebar.getChannelByDisplayName(newChannel.display_name);
@ -43,7 +41,7 @@ describe('Select channel', () => {
await expect(channelNavBarTitle).toHaveText(newChannel.display_name);
// # Open main sidebar again and select the same channel
await mainSidebarDrawerButton.tap();
await ChannelScreen.openMainSidebar();
await channelItem.tap();
// * Drawer should not be visible on Android

View file

@ -14,6 +14,7 @@ describe('Messaging', () => {
beforeAll(async () => {
const {user} = await Setup.apiInit();
// # Open channel screen
await ChannelScreen.open(user);
});
@ -21,28 +22,34 @@ describe('Messaging', () => {
await ChannelScreen.logout();
});
it('should post a message on tap to paper send button', async () => {
it('MM-T3486 should post a message when send button is tapped', async () => {
// * Verify channel screen is visible
await ChannelScreen.toBeVisible();
const {postInput, sendButton} = ChannelScreen;
const {
postInput,
sendButton,
sendButtonDisabled,
} = ChannelScreen;
// * Post input should exist while send button should not
// * Verify post input is visible and send button is disabled
await expect(postInput).toBeVisible();
await expect(sendButton).not.toExist();
await expect(sendButtonDisabled).toBeVisible();
// # Tap on post input
await postInput.tap();
// # Type text on post input
const text = Date.now().toString();
await postInput.typeText(text);
// # Type message on post input
const message = Date.now().toString();
await postInput.typeText(message);
// * Verify send button is enabled
await expect(sendButton).toBeVisible();
// # Tap send button
await expect(sendButton).toBeVisible();
await sendButton.tap();
// * Verify text to exist
await expect(element(by.text(text))).toBeVisible();
// * Verify message is posted
await expect(element(by.text(message))).toBeVisible();
});
});

View file

@ -19,6 +19,7 @@ describe('Messaging', () => {
beforeAll(async () => {
({team, user} = await Setup.apiInit());
// # Open channel screen
await ChannelScreen.open(user);
});
@ -29,21 +30,21 @@ describe('Messaging', () => {
it('MM-T109 User can\'t send the same message repeatedly', async () => {
const message = Date.now().toString();
const {disabledSendButton, sendButton, postInput} = ChannelScreen;
const {
postInput,
sendButtonDisabled,
} = ChannelScreen;
// # Type a message
await postInput.tap();
await postInput.typeText(message);
// # Tap the send button
await expect(sendButton).toBeVisible();
await sendButton.tap();
await expect(sendButton).not.toExist();
// # Tap send button
await ChannelScreen.tapSendButton();
// # Then tap send button repeatedly
await expect(disabledSendButton).toBeVisible();
await expect(disabledSendButton).toExist();
await disabledSendButton.multiTap(3);
await expect(sendButtonDisabled).toBeVisible();
await sendButtonDisabled.multiTap(3);
// * Check that message is successfully posted
await expect(element(by.text(message))).toExist();

View file

@ -7,10 +7,7 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {
MainSidebar,
SettingsSidebar,
} from '@support/ui/component';
import {MainSidebar} from '@support/ui/component';
import {
ChannelInfoScreen,
ChannelNotificationPreferenceScreen,
@ -28,15 +25,14 @@ describe('Channel Notification Preference - Default', () => {
beforeAll(async () => {
const {user, channel} = await Setup.apiInit();
testChannel = channel;
// # Open channel screen
await ChannelScreen.open(user);
});
beforeEach(async () => {
// # Open main sidebar
await ChannelScreen.mainSidebarDrawerButton.tap();
await MainSidebar.toBeVisible();
// # Go to channel
await ChannelScreen.openMainSidebar();
await MainSidebar.getChannelByDisplayName(testChannel.display_name).tap();
});
@ -53,11 +49,9 @@ describe('Channel Notification Preference - Default', () => {
// # Set global notifications to mentions
await setGlobalNotificationsTo('mentions');
// # Open channel info screen
// # Open on notification preference screen
await ChannelInfoScreen.open();
// # Tap on Mobile Notifications
await ChannelInfoScreen.notificationPreferenceAction.tap();
await ChannelNotificationPreferenceScreen.open();
// # Tap on For all activity option
await element(by.text(`${ChannelNotificationPreferenceScreen.optionDefaultText} (Mentions)`)).tap();
@ -76,7 +70,7 @@ describe('Channel Notification Preference - Default', () => {
// * Verify Global default (Never) is displayed
await ChannelInfoScreen.open();
await ChannelInfoScreen.notificationPreferenceAction.tap();
await ChannelNotificationPreferenceScreen.open();
await expect(element(by.text(`${ChannelNotificationPreferenceScreen.optionDefaultText} (Never)`))).toBeVisible();
// # Go back to channel info screen
@ -88,7 +82,7 @@ describe('Channel Notification Preference - Default', () => {
// * Verify Global default (All) is displayed
await ChannelInfoScreen.open();
await ChannelInfoScreen.notificationPreferenceAction.tap();
await ChannelNotificationPreferenceScreen.open();
await expect(element(by.text(`${ChannelNotificationPreferenceScreen.optionDefaultText} (All)`))).toBeVisible();
// # Tap on back button
@ -97,21 +91,11 @@ describe('Channel Notification Preference - Default', () => {
});
async function setGlobalNotificationsTo(pushKey) {
// # Open settings sidebar
await ChannelScreen.settingsSidebarDrawerButton.tap();
await SettingsSidebar.toBeVisible();
// # Open general settings screen
await SettingsSidebar.settingsAction.tap();
await GeneralSettingsScreen.toBeVisible();
// # Open notification settings screen
await GeneralSettingsScreen.notificationsAction.tap();
await NotificationSettingsScreen.toBeVisible();
// # Open notifications settings mobile screen
await NotificationSettingsScreen.mobileAction.tap();
await NotificationSettingsMobileScreen.toBeVisible();
await ChannelScreen.openSettingsSidebar();
await GeneralSettingsScreen.open();
await NotificationSettingsScreen.open();
await NotificationSettingsMobileScreen.open();
// # Tap on Send Notifications option if Android
if (isAndroid()) {

View file

@ -12,7 +12,7 @@ import {
ChannelInfoScreen,
ChannelNotificationPreferenceScreen,
ChannelScreen,
DirectChannelsScreen,
MoreDirectMessagesScreen,
} from '@support/ui/screen';
import {Setup} from '@support/server_api';
import {isAndroid} from '@support/utils';
@ -23,15 +23,14 @@ describe('Channel Notification Preference', () => {
beforeAll(async () => {
const {user, channel} = await Setup.apiInit();
testChannel = channel;
// # Open channel screen
await ChannelScreen.open(user);
});
beforeEach(async () => {
// # Open main sidebar
await ChannelScreen.mainSidebarDrawerButton.tap();
await MainSidebar.toBeVisible();
// # Go to channel
await ChannelScreen.openMainSidebar();
await MainSidebar.getChannelByDisplayName(testChannel.display_name).tap();
});
@ -45,11 +44,9 @@ describe('Channel Notification Preference', () => {
});
it('MM-T3375_1 should display Mobile Notifications option for non-DM Channels', async () => {
// # Open channel info screen
// # Open on notification preference screen
await ChannelInfoScreen.open();
// # Tap on Mobile Notifications
await ChannelInfoScreen.notificationPreferenceAction.tap();
await ChannelNotificationPreferenceScreen.open();
const {
titleText,
@ -73,18 +70,14 @@ describe('Channel Notification Preference', () => {
});
it('MM-T3375_2 should not display Mobile Notifications option for DM Channel', async () => {
// # Open main sidebar
await ChannelScreen.mainSidebarDrawerButton.tap();
await MainSidebar.toBeVisible();
// # Open Direct Channels screen
await MainSidebar.addDirectChannel.tap();
await DirectChannelsScreen.toBeVisible();
// # Open more direct messages screen
await ChannelScreen.openMainSidebar();
await MoreDirectMessagesScreen.open();
const {
getUserAtIndex,
startButton,
} = DirectChannelsScreen;
} = MoreDirectMessagesScreen;
// # Select 1 profile
await getUserAtIndex(0).tap();
@ -100,11 +93,9 @@ describe('Channel Notification Preference', () => {
});
it('MM-T3377 should be able to select For all activity option and display All next to Mobile Notifications', async () => {
// # Open channel info screen
// # Open on notification preference screen
await ChannelInfoScreen.open();
// # Tap on Mobile Notifications
await ChannelInfoScreen.notificationPreferenceAction.tap();
await ChannelNotificationPreferenceScreen.open();
// # Tap on For all activity option
await element(by.text(ChannelNotificationPreferenceScreen.optionAllText)).tap();
@ -118,11 +109,9 @@ describe('Channel Notification Preference', () => {
});
it('MM-T3378 should be able to select Only mentions and direct messages option and display Mentions next to Mobile Notifications', async () => {
// # Open channel info screen
// # Open on notification preference screen
await ChannelInfoScreen.open();
// # Tap on Mobile Notifications
await ChannelInfoScreen.notificationPreferenceAction.tap();
await ChannelNotificationPreferenceScreen.open();
// # Tap on For all activity option
await element(by.text(ChannelNotificationPreferenceScreen.optionMentionsText)).tap();
@ -136,11 +125,9 @@ describe('Channel Notification Preference', () => {
});
it('MM-T3379 should be able to select Never option and display Never next to Mobile Notifications', async () => {
// # Open channel info screen
// # Open on notification preference screen
await ChannelInfoScreen.open();
// # Tap on Mobile Notifications
await ChannelInfoScreen.notificationPreferenceAction.tap();
await ChannelNotificationPreferenceScreen.open();
// # Tap on For all activity option
await element(by.text(ChannelNotificationPreferenceScreen.optionNeverText)).tap();

View file

@ -7,12 +7,94 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {AddReactionScreen, ChannelScreen, NotificationScreen} from '@support/ui/screen';
import {isAndroid, timeouts, wait} from '@support/utils';
import {Setup} from '@support/server_api';
import {
AddReactionScreen,
ChannelScreen,
NotificationScreen,
} from '@support/ui/screen';
import {
Channel,
Post,
Setup,
} from '@support/server_api';
import {
isAndroid,
timeouts,
wait,
} from '@support/utils';
const DetoxConstants = require('detox').DetoxConstants;
describe('in-app Notification', () => {
let testChannel1;
let testChannel2;
let testNotification;
beforeAll(async () => {
const {channel, team, user} = await Setup.apiInit();
testChannel1 = channel;
testNotification = getNotification(testChannel1, team, user);
({channel: testChannel2} = await Channel.apiGetChannelByName(team.name, 'town-square'));
// # Open channel screen
await ChannelScreen.open(user);
});
afterAll(async () => {
await ChannelScreen.logout();
});
it('MM-T3440 should render an in-app notification', async () => {
const testMessage = Date.now().toString();
const {postInput} = ChannelScreen;
// # Type a message
await postInput.tap();
await postInput.typeText(testMessage);
// # Tap send button
await ChannelScreen.tapSendButton();
// # Open add reaction screen
const {post} = await Post.apiGetLastPostInChannel(testChannel2.id);
await ChannelScreen.openPostOptionsFor(post.id, testMessage);
await AddReactionScreen.open();
// # Close add reaction screen
await AddReactionScreen.close();
if (isAndroid()) {
// eslint-disable-next-line no-console
console.log('Skipping on Android until https://github.com/wix/Detox/issues/2141');
return;
}
// # When a push notification is received
await device.sendUserNotification(testNotification);
await wait(timeouts.HALF_SEC);
// * Verify in-app notification is shown
const {
inAppNotificationScreen,
inAppNotificationIcon,
inAppNotificationTitle,
inAppNotificationMessage,
} = NotificationScreen;
await NotificationScreen.toBeVisible();
await expect(inAppNotificationIcon).toBeVisible();
await expect(inAppNotificationTitle).toHaveText(testChannel1.name);
await expect(inAppNotificationMessage).toHaveText('This is an e2e test message');
// # Wait for some profiles to load
await wait(5 * timeouts.ONE_SEC);
// * Verify in-app notification is hidden
await expect(inAppNotificationScreen).not.toBeVisible();
});
});
function getNotification(channel, team, user) {
if (isAndroid()) {
return {
@ -52,66 +134,3 @@ function getNotification(channel, team, user) {
},
};
}
describe('in-app Notification', () => {
let testNotification;
let testChannel;
beforeAll(async () => {
const {channel, team, user} = await Setup.apiInit();
testChannel = channel;
testNotification = getNotification(channel, team, user);
await ChannelScreen.open(user);
});
afterAll(async () => {
await ChannelScreen.logout();
});
it('MM-T3440 should render an in-app notification', async () => {
const message = Date.now().toString();
const {postInput, sendButton} = ChannelScreen;
// # Type a message
await postInput.tap();
await postInput.typeText(message);
// # Tap the send button
await sendButton.tap();
await wait(timeouts.HALF_SEC);
// # Open Add reaction screen
await AddReactionScreen.open(message);
await AddReactionScreen.closeAddReactionButton.tap();
if (isAndroid()) {
// eslint-disable-next-line no-console
console.log('Skipping on Android until https://github.com/wix/Detox/issues/2141');
return;
}
// # When a push notification is received
await device.sendUserNotification(testNotification);
await wait(timeouts.HALF_SEC);
// * Verify in-app notification is shown
const {
inAppNotification,
inAppNotificationIcon,
inAppNotificationTitle,
inAppNotificationMessage,
} = NotificationScreen;
await NotificationScreen.toBeVisible();
await expect(inAppNotificationIcon).toBeVisible();
await expect(inAppNotificationTitle).toHaveText(testChannel.name);
await expect(inAppNotificationMessage).toHaveText('This is an e2e test message');
// # Wait for some profiles to load
await wait(5 * timeouts.ONE_SEC);
// * Verify in-app notification is hidden
await expect(inAppNotification).not.toBeVisible();
});
});

View file

@ -24,6 +24,7 @@ describe('Smoke Tests', () => {
// * Check that LDAP server can connect and is synchronized with Mattermost server
await Ldap.apiRequireLDAPServer();
// # Ensure user has team
await ensureUserHasTeam(testOne);
});

View file

@ -0,0 +1,79 @@
// 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 {PostOptions} from '@support/ui/component';
import {
ChannelScreen,
RecentMentionsScreen,
} from '@support/ui/screen';
import {
Channel,
Post,
Setup,
Team,
User,
} from '@support/server_api';
describe('Recent Mentions', () => {
let testUser1;
let testUser2;
let testChannel;
beforeAll(async () => {
const {team, user} = await Setup.apiInit();
testUser1 = user;
const {channel} = await Channel.apiGetChannelByName(team.name, 'town-square');
testChannel = channel;
({user: testUser2} = await User.apiCreateUser());
await Team.apiAddUserToTeam(testUser2.id, team.id);
await Channel.apiAddUserToChannel(testUser2.id, testChannel.id);
// # Open channel screen
await ChannelScreen.open(testUser1);
});
afterAll(async () => {
await ChannelScreen.logout();
});
it('MM-T3222 should display recent mentions and no option to add a reaction', async () => {
// # Post an at-mention message to user 1 by user 2
const testMessage = `Mention @${testUser1.username}`;
await User.apiLogin(testUser2);
await Post.apiCreatePost({
channelId: testChannel.id,
message: testMessage,
});
// * Verify most recent post has the message
const lastPost = await Post.apiGetLastPostInChannel(testChannel.id);
const postListPostItem = await ChannelScreen.getPostListPostItem(lastPost.post.id, testMessage);
await expect(postListPostItem).toBeVisible();
// # Open recent mentions screen
await ChannelScreen.openSettingsSidebar();
await RecentMentionsScreen.open();
// * Verify most recent search result post has the at-mention message
const lastSearchResultPost = await Post.apiGetLastPostInChannel(testChannel.id);
const searchResultPostItem = await RecentMentionsScreen.getSearchResultPostItem(lastSearchResultPost.post.id, testMessage);
await expect(searchResultPostItem).toBeVisible();
// * Verify recent mentions post item does not have option to add a reaction
await searchResultPostItem.longPress();
await PostOptions.toBeVisible();
await expect(PostOptions.reactionPickerAction).not.toBeVisible();
// # Close recent mentions screen
await PostOptions.close();
await RecentMentionsScreen.close();
});
});

View file

@ -7,9 +7,12 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {ChannelScreen} from '@support/ui/screen';
import {MainSidebar} from '@support/ui/component';
import {Setup, Channel} from '@support/server_api';
import {ChannelScreen} from '@support/ui/screen';
import {
Channel,
Setup,
} from '@support/server_api';
describe('Unread channels', () => {
let newChannel;
@ -26,6 +29,7 @@ describe('Unread channels', () => {
({channel: zChannel} = await Channel.apiCreateChannel({type: 'O', prefix: 'z-channel', teamId: team.id}));
await Channel.apiAddUserToChannel(user.id, zChannel.id);
// # Open channel screen
await ChannelScreen.open(user);
});
@ -34,13 +38,8 @@ describe('Unread channels', () => {
});
it('MM-T3187 Unread channels sort at top', async () => {
const {mainSidebarDrawerButton} = ChannelScreen;
// # Open main sidebar (with at least one unread channel)
await mainSidebarDrawerButton.tap();
// * Channel should be visible
await MainSidebar.toBeVisible();
await ChannelScreen.openMainSidebar();
// * Verify unread channel(s) display at top of channel list (with mentions first, if any), in alphabetical order, with title "Unreads"
await expect(element(by.text('UNREADS'))).toBeVisible();
@ -50,15 +49,13 @@ describe('Unread channels', () => {
// # Tap an unread channel to view it
await MainSidebar.getChannelByDisplayName(aChannel.display_name).tap();
await mainSidebarDrawerButton.tap();
await ChannelScreen.openMainSidebar();
await MainSidebar.getChannelByDisplayName(newChannel.display_name).tap();
await mainSidebarDrawerButton.tap();
await ChannelScreen.openMainSidebar();
await MainSidebar.getChannelByDisplayName(zChannel.display_name).tap();
// # Open main sidebar again
await mainSidebarDrawerButton.tap();
// * Channel you just read is no longer listed in Unreads
await ChannelScreen.openMainSidebar();
await expect(element(by.text('UNREADS'))).not.toBeVisible();
await expect(element(by.text('PUBLIC CHANNELS'))).toBeVisible();
await MainSidebar.hasChannelAtIndex(0, aChannel.display_name);