MM-30427 Detox/E2E: Add e2e for MM-T3197, MM-T3225, MM-T3239, MM-T3274 (#5230)
This commit is contained in:
parent
5d8632971b
commit
78c05270ba
18 changed files with 299 additions and 3 deletions
|
|
@ -39,6 +39,7 @@ export default class Emoji extends React.PureComponent {
|
|||
textStyle: CustomPropTypes.Style,
|
||||
unicode: PropTypes.string,
|
||||
customEmojiStyle: CustomPropTypes.Style,
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -55,6 +56,7 @@ export default class Emoji extends React.PureComponent {
|
|||
imageUrl,
|
||||
literal,
|
||||
unicode,
|
||||
testID,
|
||||
textStyle,
|
||||
} = this.props;
|
||||
|
||||
|
|
@ -67,7 +69,13 @@ export default class Emoji extends React.PureComponent {
|
|||
}
|
||||
|
||||
if (displayTextOnly) {
|
||||
return <Text style={textStyle}>{literal}</Text>;
|
||||
return (
|
||||
<Text
|
||||
style={textStyle}
|
||||
testID={testID}
|
||||
>
|
||||
{literal}
|
||||
</Text>);
|
||||
}
|
||||
|
||||
const width = size;
|
||||
|
|
@ -84,7 +92,10 @@ export default class Emoji extends React.PureComponent {
|
|||
}, '');
|
||||
|
||||
return (
|
||||
<Text style={[textStyle, {fontSize: size}]}>
|
||||
<Text
|
||||
style={[textStyle, {fontSize: size}]}
|
||||
testID={testID}
|
||||
>
|
||||
{code}
|
||||
</Text>
|
||||
);
|
||||
|
|
@ -101,6 +112,7 @@ export default class Emoji extends React.PureComponent {
|
|||
source={{uri: imageUrl}}
|
||||
onError={this.onError}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,6 +259,7 @@ export default class Markdown extends PureComponent {
|
|||
<Emoji
|
||||
emojiName={emojiName}
|
||||
literal={literal}
|
||||
testID='markdown_emoji'
|
||||
textStyle={this.computeTextStyle(this.props.baseTextStyle, context)}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ export default class MarkdownEmoji extends PureComponent {
|
|||
<Emoji
|
||||
emojiName={emojiName}
|
||||
literal={literal}
|
||||
testID='markdown_emoji'
|
||||
textStyle={this.computeTextStyle(this.props.baseTextStyle, context)}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ export default class ChannelAddMembers extends PureComponent {
|
|||
text: context.intl.formatMessage({id: 'integrations.add', defaultMessage: 'Add'}),
|
||||
color: props.theme.sidebarHeaderTextColor,
|
||||
showAsAction: 'always',
|
||||
testID: 'channel_add_members.add.button',
|
||||
};
|
||||
|
||||
setButtons(props.componentId, {
|
||||
|
|
@ -369,6 +370,7 @@ export default class ChannelAddMembers extends PureComponent {
|
|||
onLoadMore={this.getProfiles}
|
||||
onRowPress={this.handleSelectProfile}
|
||||
renderItem={this.renderItem}
|
||||
testID='channel_add_members.custom_list'
|
||||
theme={theme}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ exports[`Search should match snapshot 1`] = `
|
|||
"flex": 1,
|
||||
}
|
||||
}
|
||||
testID="search.results_list"
|
||||
updateCellsBatchingPeriod={50}
|
||||
windowSize={21}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -757,6 +757,7 @@ export default class Search extends PureComponent {
|
|||
scrollEventThrottle={60}
|
||||
ListFooterComponent={this.renderFooter}
|
||||
onViewableItemsChanged={this.onViewableItemsChanged}
|
||||
testID='search.results_list'
|
||||
/>
|
||||
</SafeAreaView>
|
||||
<Autocomplete
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import ProfilePicture from './profile_picture';
|
|||
class Post {
|
||||
testID = {
|
||||
postProfilePicturePrefix: 'post_profile_picture.profile_picture.',
|
||||
emoji: 'markdown_emoji',
|
||||
image: 'markdown_image',
|
||||
message: 'markdown_text',
|
||||
postHeaderDateTime: 'post_header.date_time',
|
||||
|
|
@ -19,6 +20,7 @@ class Post {
|
|||
|
||||
getPost = (postItemSourceTestID, postId, postMessage, postProfileOptions = {}) => {
|
||||
const postItemMatcher = this.getPostItemMatcher(postItemSourceTestID, postId, postMessage);
|
||||
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);
|
||||
|
|
@ -27,6 +29,7 @@ class Post {
|
|||
|
||||
return {
|
||||
postItem: element(postItemMatcher),
|
||||
postItemEmoji: element(postItemEmojiMatcher),
|
||||
postItemImage: element(postItemImageMatcher),
|
||||
postItemMessage: element(postItemMessageMatcher),
|
||||
postItemPreHeaderText: element(postItemPreHeaderTextMatch),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class PostList {
|
|||
getPost = (postId, postMessage, postProfileOptions = {}) => {
|
||||
const {
|
||||
postItem,
|
||||
postItemEmoji,
|
||||
postItemHeaderDateTime,
|
||||
postItemHeaderDisplayName,
|
||||
postItemHeaderGuestTag,
|
||||
|
|
@ -28,6 +29,7 @@ class PostList {
|
|||
|
||||
return {
|
||||
postListPostItem: postItem,
|
||||
postListPostItemEmoji: postItemEmoji,
|
||||
postListPostItemHeaderDateTime: postItemHeaderDateTime,
|
||||
postListPostItemHeaderDisplayName: postItemHeaderDisplayName,
|
||||
postListPostItemHeaderGuestTag: postItemHeaderGuestTag,
|
||||
|
|
|
|||
104
detox/e2e/support/ui/screen/channel_add_members.js
Normal file
104
detox/e2e/support/ui/screen/channel_add_members.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {
|
||||
Alert,
|
||||
MainSidebar,
|
||||
SearchBar,
|
||||
} from '@support/ui/component';
|
||||
import {timeouts, wait} from '@support/utils';
|
||||
|
||||
class ChannelAddMembersScreen {
|
||||
testID = {
|
||||
channelAddMembersScreenPrefix: 'channel_add_members.',
|
||||
channelAddMembersScreen: 'channel_add_members.screen',
|
||||
addButton: 'channel_add_members.add.button',
|
||||
backButton: 'screen.back.button',
|
||||
usersList: 'channel_add_members.custom_list',
|
||||
userItem: 'channel_add_members.custom_list.user_item',
|
||||
userItemDisplayUsername: 'channel_add_members.custom_list.user_item.display_username',
|
||||
}
|
||||
|
||||
channelAddMembersScreen = element(by.id(this.testID.channelAddMembersScreen));
|
||||
addButton = element(by.id(this.testID.addButton));
|
||||
backButton = element(by.id(this.testID.backButton));
|
||||
usersList = element(by.id(this.testID.usersList));
|
||||
|
||||
// convenience props
|
||||
searchBar = SearchBar.getSearchBar(this.testID.channelAddMembersScreenPrefix);
|
||||
searchInput = SearchBar.getSearchInput(this.testID.channelAddMembersScreenPrefix);
|
||||
cancelButton = SearchBar.getCancelButton(this.testID.channelAddMembersScreenPrefix);
|
||||
clearButton = SearchBar.getClearButton(this.testID.channelAddMembersScreenPrefix);
|
||||
|
||||
getUser = (userId, diplayUsername) => {
|
||||
const userItemTestID = `${this.testID.userItem}.${userId}`;
|
||||
const baseMatcher = by.id(userItemTestID);
|
||||
const userItemMatcher = diplayUsername ? baseMatcher.withDescendant(by.text(diplayUsername)) : baseMatcher;
|
||||
const userItemUsernameDisplayMatcher = by.id(this.testID.userItemDisplayUsername).withAncestor(userItemMatcher);
|
||||
|
||||
return {
|
||||
userItem: element(userItemMatcher),
|
||||
userItemUsernameDisplay: element(userItemUsernameDisplayMatcher),
|
||||
};
|
||||
}
|
||||
|
||||
getUserAtIndex = (index) => {
|
||||
return element(by.id(this.testID.userItem).withAncestor(by.id(this.testID.usersList))).atIndex(index);
|
||||
}
|
||||
|
||||
getUserByDisplayUsername = (displayUsername) => {
|
||||
return element(by.text(displayUsername).withAncestor(by.id(this.testID.usersList)));
|
||||
}
|
||||
|
||||
getDisplayUsernameAtIndex = (index) => {
|
||||
return element(by.id(this.testID.userItemDisplayUsername)).atIndex(index);
|
||||
}
|
||||
|
||||
toBeVisible = async () => {
|
||||
await expect(this.channelAddMembersScreen).toBeVisible();
|
||||
|
||||
return this.channelAddMembersScreen;
|
||||
}
|
||||
|
||||
open = async () => {
|
||||
// # Open more direct messages screen
|
||||
await MainSidebar.openChannelMembersButton.tap();
|
||||
|
||||
return this.toBeVisible();
|
||||
}
|
||||
|
||||
back = async () => {
|
||||
await this.backButton.tap();
|
||||
await expect(this.channelAddMembersScreen).not.toBeVisible();
|
||||
}
|
||||
|
||||
removeMembers = async (displayUsernameList, confirm = true) => {
|
||||
displayUsernameList.forEach(async (displayUsername) => {
|
||||
await this.getUserByDisplayUsername(`@${displayUsername}`).tap();
|
||||
});
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await this.removeButton.tap();
|
||||
const {
|
||||
removeMembersTitle,
|
||||
noButton,
|
||||
yesButton,
|
||||
} = Alert;
|
||||
await expect(removeMembersTitle).toBeVisible();
|
||||
if (confirm) {
|
||||
yesButton.tap();
|
||||
} else {
|
||||
noButton.tap();
|
||||
}
|
||||
await wait(timeouts.ONE_SEC);
|
||||
await expect(this.channelAddMembersScreen).not.toBeVisible();
|
||||
}
|
||||
|
||||
hasUserDisplayUsernameAtIndex = async (index, displayUsername) => {
|
||||
await expect(
|
||||
this.getDisplayUsernameAtIndex(index),
|
||||
).toHaveText(displayUsername);
|
||||
}
|
||||
}
|
||||
|
||||
const channelAddMembersScreen = new ChannelAddMembersScreen();
|
||||
export default channelAddMembersScreen;
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import AddReactionScreen from './add_reaction';
|
||||
import ChannelInfoScreen from './channel_info';
|
||||
import ChannelAddMembersScreen from './channel_add_members';
|
||||
import ChannelMembersScreen from './channel_members';
|
||||
import ChannelNotificationPreferenceScreen from './channel_notification_preference';
|
||||
import ChannelScreen from './channel';
|
||||
|
|
@ -33,6 +34,7 @@ import ThreadScreen from './thread';
|
|||
export {
|
||||
AddReactionScreen,
|
||||
ChannelInfoScreen,
|
||||
ChannelAddMembersScreen,
|
||||
ChannelMembersScreen,
|
||||
ChannelNotificationPreferenceScreen,
|
||||
ChannelScreen,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class LongPostScreen {
|
|||
getPost = (postId, postMessage, postProfileOptions = {}) => {
|
||||
const {
|
||||
postItem,
|
||||
postItemEmoji,
|
||||
postItemHeaderDateTime,
|
||||
postItemHeaderDisplayName,
|
||||
postItemHeaderGuestTag,
|
||||
|
|
@ -28,6 +29,7 @@ class LongPostScreen {
|
|||
|
||||
return {
|
||||
longPostItem: postItem,
|
||||
longPostItemEmoji: postItemEmoji,
|
||||
longPostItemHeaderDateTime: postItemHeaderDateTime,
|
||||
longPostItemHeaderDisplayName: postItemHeaderDisplayName,
|
||||
longPostItemHeaderGuestTag: postItemHeaderGuestTag,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class SearchScreen {
|
|||
searchOnSection: 'search.on_section',
|
||||
searchAfterSection: 'search.after_section',
|
||||
searchBeforeSection: 'search.before_section',
|
||||
searchResultsList: 'search.results_list',
|
||||
}
|
||||
|
||||
searchScreen = element(by.id(this.testID.searchScreen));
|
||||
|
|
@ -28,6 +29,7 @@ class SearchScreen {
|
|||
searchOnSection = element(by.id(this.testID.searchOnSection));
|
||||
searchAfterSection = element(by.id(this.testID.searchAfterSection));
|
||||
searchBeforeSection = element(by.id(this.testID.searchBeforeSection));
|
||||
searchResultsList = element(by.id(this.testID.searchResultsList));
|
||||
|
||||
searchFromModifier = 'from:';
|
||||
searchInModifier = 'in:';
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ class SearchResultPostScreen {
|
|||
getPost = (postId, postMessage, postProfileOptions = {}) => {
|
||||
const {
|
||||
postItem,
|
||||
postItemEmoji,
|
||||
postItemHeaderDateTime,
|
||||
postItemHeaderDisplayName,
|
||||
postItemHeaderGuestTag,
|
||||
|
|
@ -25,6 +26,7 @@ class SearchResultPostScreen {
|
|||
|
||||
return {
|
||||
searchResultPostItem: postItem,
|
||||
searchResultPostItemEmoji: postItemEmoji,
|
||||
searchResultPostItemHeaderDateTime: postItemHeaderDateTime,
|
||||
searchResultPostItemHeaderDisplayName: postItemHeaderDisplayName,
|
||||
searchResultPostItemHeaderGuestTag: postItemHeaderGuestTag,
|
||||
|
|
|
|||
63
detox/e2e/test/smoke_test/channel_add_members.e2e.js
Normal file
63
detox/e2e/test/smoke_test/channel_add_members.e2e.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// 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 {
|
||||
ChannelAddMembersScreen,
|
||||
ChannelInfoScreen,
|
||||
ChannelScreen,
|
||||
} from '@support/ui/screen';
|
||||
import {
|
||||
Setup,
|
||||
Team,
|
||||
User,
|
||||
} from '@support/server_api';
|
||||
|
||||
describe('Channel Add Members', () => {
|
||||
let testChannel;
|
||||
let testLastUser;
|
||||
|
||||
beforeAll(async () => {
|
||||
const {channel, team, user} = await Setup.apiInit();
|
||||
testChannel = channel;
|
||||
|
||||
[...Array(100).keys()].forEach(async (key) => {
|
||||
const {user: testOtherUser} = await User.apiCreateUser({prefix: `a-${key}-`});
|
||||
await Team.apiAddUserToTeam(testOtherUser.id, team.id);
|
||||
});
|
||||
({user: testLastUser} = await User.apiCreateUser({prefix: 'z-'}));
|
||||
await Team.apiAddUserToTeam(testLastUser.id, team.id);
|
||||
|
||||
// # Open channel screen
|
||||
await ChannelScreen.open(user);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await ChannelScreen.logout();
|
||||
});
|
||||
|
||||
it('MM-T3197 should be able load users in long add members list', async () => {
|
||||
const {
|
||||
addMembersAction,
|
||||
channelInfoScrollView,
|
||||
} = ChannelInfoScreen;
|
||||
|
||||
// # Open add members screen
|
||||
await ChannelScreen.goToChannel(testChannel.display_name);
|
||||
await ChannelInfoScreen.open();
|
||||
await channelInfoScrollView.scrollTo('bottom');
|
||||
await addMembersAction.tap();
|
||||
|
||||
// * Verify user can scroll down multiple times until last user is seen
|
||||
await waitFor(ChannelAddMembersScreen.getUserByDisplayUsername(`@${testLastUser.username}`)).toBeVisible().whileElement(by.id(ChannelAddMembersScreen.testID.usersList)).scroll(500, 'down');
|
||||
await ChannelAddMembersScreen.back();
|
||||
|
||||
// # Close channel info screen
|
||||
await ChannelInfoScreen.close();
|
||||
});
|
||||
});
|
||||
|
|
@ -20,7 +20,7 @@ import {
|
|||
} from '@support/server_api';
|
||||
import {getRandomId} from '@support/utils';
|
||||
|
||||
describe('Manage Members', () => {
|
||||
describe('Channel Manage Members', () => {
|
||||
const searchTerm = getRandomId();
|
||||
const {
|
||||
removeMembers,
|
||||
|
|
@ -78,4 +78,15 @@ describe('Message Posting', () => {
|
|||
const {postListPostItemImage} = await getPostListPostItem(post.id);
|
||||
await expect(postListPostItemImage).toBeVisible();
|
||||
});
|
||||
|
||||
it('MM-T3225 should be able to post a jumbo emoji', async () => {
|
||||
// # Post a jumbo emoji
|
||||
const message = ':fox_face:';
|
||||
await postMessage(message, {quickReplace: true});
|
||||
|
||||
// * Verify message is posted
|
||||
const {post} = await Post.apiGetLastPostInChannel(testChannel.id);
|
||||
const {postListPostItem} = await getPostListPostItem(post.id);
|
||||
await expect(postListPostItem).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ describe('Search', () => {
|
|||
getSearchResultPostItem,
|
||||
searchFromModifier,
|
||||
searchInModifier,
|
||||
searchInput,
|
||||
} = SearchScreen;
|
||||
let testMessage;
|
||||
let testPartialSearchTerm;
|
||||
|
|
@ -174,6 +175,36 @@ describe('Search', () => {
|
|||
// # Go back to channel
|
||||
await SearchScreen.cancel();
|
||||
});
|
||||
|
||||
it('MM-T3239 should be able to scroll through long list of search results', async () => {
|
||||
// # Post messages
|
||||
const keyword = 'qa';
|
||||
const firstMessage = `${Date.now().toString()} ${keyword} first`;
|
||||
const firstPost = await Post.apiCreatePost({
|
||||
channelId: testChannel.id,
|
||||
message: firstMessage,
|
||||
});
|
||||
[...Array(50).keys()].forEach(async () => {
|
||||
const message = `${Date.now().toString()} ${keyword}`;
|
||||
await Post.apiCreatePost({
|
||||
channelId: testChannel.id,
|
||||
message,
|
||||
});
|
||||
});
|
||||
|
||||
// # Perform search on keyword
|
||||
await SearchScreen.open();
|
||||
await searchInput.clearText();
|
||||
await searchInput.typeText(keyword);
|
||||
await searchInput.tapReturnKey();
|
||||
|
||||
// * Verify user can scroll down multiple times until first matching post is seen
|
||||
const {searchResultPostItem} = await getSearchResultPostItem(firstPost.post.id, firstMessage);
|
||||
await waitFor(searchResultPostItem).toBeVisible().whileElement(by.id(SearchScreen.testID.searchResultsList)).scroll(1000, 'down');
|
||||
|
||||
// # Go back to channel
|
||||
await SearchScreen.cancel();
|
||||
});
|
||||
});
|
||||
|
||||
async function postMessageAndSearchFrom(testMessage, testUser, atMentionSuggestionList, testPartialSearchTerm) {
|
||||
|
|
|
|||
56
detox/e2e/test/smoke_test/select_team.e2e.js
Normal file
56
detox/e2e/test/smoke_test/select_team.e2e.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// 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 {
|
||||
ChannelScreen,
|
||||
LoginScreen,
|
||||
SelectTeamScreen,
|
||||
} from '@support/ui/screen';
|
||||
import {
|
||||
Channel,
|
||||
Team,
|
||||
User,
|
||||
} from '@support/server_api';
|
||||
|
||||
describe('Select Team', () => {
|
||||
const {getTeamByDisplayName} = SelectTeamScreen;
|
||||
let testTeam;
|
||||
|
||||
beforeAll(async () => {
|
||||
await Team.apiPatchTeams({allow_open_invite: false});
|
||||
const {user} = await User.apiCreateUser();
|
||||
const {team} = await Team.apiCreateTeam();
|
||||
testTeam = team;
|
||||
await Team.apiPatchTeam(testTeam.id, {allow_open_invite: true});
|
||||
|
||||
// # Login
|
||||
await LoginScreen.open();
|
||||
await LoginScreen.login(user);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await ChannelScreen.logout();
|
||||
await Team.apiPatchTeams({allow_open_invite: true});
|
||||
});
|
||||
|
||||
it('MM-T3274 user with no teams should be able to select a team', async () => {
|
||||
// * Verify select team screen is displayed
|
||||
await SelectTeamScreen.toBeVisible();
|
||||
|
||||
// # Tap on team to join
|
||||
await waitFor(getTeamByDisplayName(testTeam.display_name)).toBeVisible().whileElement(by.id(SelectTeamScreen.testID.teamsList)).scroll(500, 'down');
|
||||
const team = await getTeamByDisplayName(testTeam.display_name);
|
||||
await team.tap();
|
||||
|
||||
// * Verify redirect to default channel of joined team
|
||||
const {channel} = await Channel.apiGetChannelByName(testTeam.name, 'town-square');
|
||||
await ChannelScreen.toBeVisible();
|
||||
await expect(ChannelScreen.channelNavBarTitle).toHaveText(channel.display_name);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue