diff --git a/app/components/autocomplete/at_mention_item/at_mention_item.tsx b/app/components/autocomplete/at_mention_item/at_mention_item.tsx
index 9642703da..ec14f8622 100644
--- a/app/components/autocomplete/at_mention_item/at_mention_item.tsx
+++ b/app/components/autocomplete/at_mention_item/at_mention_item.tsx
@@ -115,6 +115,7 @@ const AtMentionItem = (props: AtMentionItemProps) => {
size={24}
status={null}
showStatus={false}
+ testID={`${testID}.profile_picture`}
/>
);
};
diff --git a/app/components/autocomplete/channel_mention_item/channel_mention_item.js b/app/components/autocomplete/channel_mention_item/channel_mention_item.js
index b78b7df54..4be107afc 100644
--- a/app/components/autocomplete/channel_mention_item/channel_mention_item.js
+++ b/app/components/autocomplete/channel_mention_item/channel_mention_item.js
@@ -47,6 +47,7 @@ const ChannelMentionItem = (props) => {
isGuest,
name,
onPress,
+ testID,
theme,
type,
} = props;
@@ -78,6 +79,7 @@ const ChannelMentionItem = (props) => {
onPress={completeMention}
style={[style.row, margins]}
type={'opacity'}
+ testID={testID}
>
{'@' + displayName}
{
style={margins}
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
type={'native'}
+ testID={testID}
>
@@ -49,6 +50,7 @@ exports[`Avatars should match snapshot for overflow 1`] = `
@@ -70,6 +72,7 @@ exports[`Avatars should match snapshot for overflow 1`] = `
@@ -147,6 +150,7 @@ exports[`Avatars should match snapshot for single avatar 1`] = `
diff --git a/app/components/avatars/avatars.tsx b/app/components/avatars/avatars.tsx
index b2af1c0d8..57192eae1 100644
--- a/app/components/avatars/avatars.tsx
+++ b/app/components/avatars/avatars.tsx
@@ -108,6 +108,7 @@ export default class Avatars extends PureComponent {
userId={userId}
size={ViewTypes.AVATAR_LIST_PICTURE_SIZE}
showStatus={false}
+ testID='avatars.profile_picture'
/>
))}
diff --git a/app/components/channel_intro/channel_intro.js b/app/components/channel_intro/channel_intro.js
index 046e9689a..c401ee08a 100644
--- a/app/components/channel_intro/channel_intro.js
+++ b/app/components/channel_intro/channel_intro.js
@@ -92,6 +92,7 @@ class ChannelIntro extends PureComponent {
iconSize={48}
statusBorderWidth={2}
statusSize={25}
+ testID='channel_intro.profile_picture'
/>
));
diff --git a/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap b/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap
index 64236e85b..e97ef94ec 100644
--- a/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap
+++ b/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap
@@ -29,6 +29,7 @@ exports[`UserListRow should match snapshot 1`] = `
@@ -164,6 +165,7 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated
@@ -297,6 +299,7 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = `
@@ -443,6 +446,7 @@ exports[`UserListRow should match snapshot for guest user 1`] = `
diff --git a/app/components/custom_list/user_list_row/user_list_row.js b/app/components/custom_list/user_list_row/user_list_row.js
index e42c0bde7..3e27e3007 100644
--- a/app/components/custom_list/user_list_row/user_list_row.js
+++ b/app/components/custom_list/user_list_row/user_list_row.js
@@ -66,6 +66,7 @@ export default class UserListRow extends React.PureComponent {
const testID = this.props.testID;
const itemTestID = `${testID}.${id}`;
const displayUsernameTestID = `${testID}.display_username`;
+ const profilePictureTestID = `${itemTestID}.profile_picture`;
return (
@@ -82,6 +83,7 @@ export default class UserListRow extends React.PureComponent {
userId={id}
size={32}
iconSize={24}
+ testID={profilePictureTestID}
/>
{
@@ -38,13 +39,20 @@ export default class FormattedTime extends React.PureComponent {
};
render() {
- const {children, style} = this.props;
+ const {children, style, testID} = this.props;
const formattedTime = this.getFormattedTime();
if (typeof children === 'function') {
return children(formattedTime);
}
- return {formattedTime};
+ return (
+
+ {formattedTime}
+
+ );
}
}
diff --git a/app/components/post_header/__snapshots__/post_header.test.js.snap b/app/components/post_header/__snapshots__/post_header.test.js.snap
index 0fa554399..37ee31856 100644
--- a/app/components/post_header/__snapshots__/post_header.test.js.snap
+++ b/app/components/post_header/__snapshots__/post_header.test.js.snap
@@ -47,6 +47,7 @@ exports[`PostHeader should match snapshot when just a base post 1`] = `
"paddingVertical": 2,
}
}
+ testID="post_header.display_name"
>
John Smith
@@ -62,6 +63,7 @@ exports[`PostHeader should match snapshot when just a base post 1`] = `
"opacity": 0.5,
}
}
+ testID="post_header.date_time"
timeZone=""
value={0}
/>
@@ -119,6 +121,7 @@ exports[`PostHeader should match snapshot when just a base post in landscape mod
"paddingVertical": 2,
}
}
+ testID="post_header.display_name"
>
John Smith
@@ -134,6 +137,7 @@ exports[`PostHeader should match snapshot when just a base post in landscape mod
"opacity": 0.5,
}
}
+ testID="post_header.date_time"
timeZone=""
value={0}
/>
@@ -187,6 +191,7 @@ exports[`PostHeader should match snapshot when post is autoresponder 1`] = `
"paddingVertical": 2,
}
}
+ testID="post_header.display_name"
>
John Smith
@@ -243,6 +248,7 @@ exports[`PostHeader should match snapshot when post is autoresponder 1`] = `
"opacity": 0.5,
}
}
+ testID="post_header.date_time"
timeZone=""
value={0}
/>
@@ -295,6 +301,7 @@ exports[`PostHeader should match snapshot when post is from system message 1`] =
},
]
}
+ testID="post_header.display_name"
/>
@@ -363,6 +371,7 @@ exports[`PostHeader should match snapshot when post is same thread, so dont disp
"paddingVertical": 2,
}
}
+ testID="post_header.display_name"
>
John Smith
@@ -378,6 +387,7 @@ exports[`PostHeader should match snapshot when post is same thread, so dont disp
"opacity": 0.5,
}
}
+ testID="post_header.date_time"
timeZone=""
value={0}
/>
@@ -452,6 +462,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
"paddingVertical": 2,
}
}
+ testID="post_header.display_name"
>
John Smith
@@ -505,6 +516,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
"opacity": 0.5,
}
}
+ testID="post_header.date_time"
timeZone=""
value={0}
/>
@@ -605,6 +617,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
"paddingVertical": 2,
}
}
+ testID="post_header.display_name"
>
John Smith
@@ -658,6 +671,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
"opacity": 0.5,
}
}
+ testID="post_header.date_time"
timeZone=""
value={0}
/>
@@ -756,6 +770,7 @@ exports[`PostHeader should match snapshot when post renders Commented On for new
"paddingVertical": 2,
}
}
+ testID="post_header.display_name"
>
John Smith
@@ -771,6 +786,7 @@ exports[`PostHeader should match snapshot when post renders Commented On for new
"opacity": 0.5,
}
}
+ testID="post_header.date_time"
timeZone=""
value={0}
/>
@@ -843,6 +859,7 @@ exports[`PostHeader should match snapshot when post should display reply button
"paddingVertical": 2,
}
}
+ testID="post_header.display_name"
>
John Smith
@@ -858,6 +875,7 @@ exports[`PostHeader should match snapshot when post should display reply button
"opacity": 0.5,
}
}
+ testID="post_header.date_time"
timeZone=""
value={0}
/>
diff --git a/app/components/post_header/post_header.js b/app/components/post_header/post_header.js
index 2a4dff729..f636d6d4e 100644
--- a/app/components/post_header/post_header.js
+++ b/app/components/post_header/post_header.js
@@ -165,6 +165,7 @@ export default class PostHeader extends PureComponent {
style={style.displayName}
ellipsizeMode={'tail'}
numberOfLines={1}
+ testID='post_header.display_name'
>
{name}
@@ -177,6 +178,7 @@ export default class PostHeader extends PureComponent {
id='post_info.system'
defaultMessage='System'
style={[style.displayName]}
+ testID='post_header.display_name'
/>
);
@@ -191,6 +193,7 @@ export default class PostHeader extends PureComponent {
style={style.displayName}
ellipsizeMode={'tail'}
numberOfLines={1}
+ testID='post_header.display_name'
>
{displayName}
@@ -204,6 +207,7 @@ export default class PostHeader extends PureComponent {
id='channel_loader.someone'
defaultMessage='Someone'
style={style.displayName}
+ testID='post_header.display_name'
/>
);
@@ -302,6 +306,7 @@ export default class PostHeader extends PureComponent {
timeZone={userTimezone}
value={createAt}
style={style.time}
+ testID='post_header.date_time'
/>
);
} else {
@@ -311,6 +316,7 @@ export default class PostHeader extends PureComponent {
hour12={!militaryTime}
value={createAt}
style={style.time}
+ testID='post_header.date_time'
/>
);
}
diff --git a/app/components/post_profile_picture/post_profile_picture.js b/app/components/post_profile_picture/post_profile_picture.js
index 23c97997e..6e003ee78 100644
--- a/app/components/post_profile_picture/post_profile_picture.js
+++ b/app/components/post_profile_picture/post_profile_picture.js
@@ -105,6 +105,7 @@ export default class PostProfilePicture extends PureComponent {
size={ViewTypes.PROFILE_PICTURE_SIZE}
iconSize={24}
showStatus={showProfileStatus}
+ testID='post_profile_picture.profile_picture'
/>
);
diff --git a/app/components/profile_picture/profile_picture.js b/app/components/profile_picture/profile_picture.js
index 3c7a0ec11..299045635 100644
--- a/app/components/profile_picture/profile_picture.js
+++ b/app/components/profile_picture/profile_picture.js
@@ -31,6 +31,7 @@ export default class ProfilePicture extends PureComponent {
profileImageUri: PropTypes.string,
profileImageRemove: PropTypes.bool,
theme: PropTypes.object.isRequired,
+ testID: PropTypes.string,
actions: PropTypes.shape({
getStatusForId: PropTypes.func.isRequired,
setProfileImageUri: PropTypes.func.isRequired,
@@ -109,7 +110,7 @@ export default class ProfilePicture extends PureComponent {
}
render() {
- const {edit, showStatus, theme, user, size} = this.props;
+ const {edit, showStatus, theme, user, size, testID} = this.props;
const {pictureUrl} = this.state;
const style = getStyleSheet(theme);
@@ -178,7 +179,10 @@ export default class ProfilePicture extends PureComponent {
}
return (
-
+
{image}
{(showStatus || edit) && (user && !user.is_bot) &&
diff --git a/app/components/sidebars/settings/status_label/status_label.js b/app/components/sidebars/settings/status_label/status_label.js
index ad4522c1c..11e9de0c2 100644
--- a/app/components/sidebars/settings/status_label/status_label.js
+++ b/app/components/sidebars/settings/status_label/status_label.js
@@ -51,6 +51,7 @@ export default class UserInfo extends PureComponent {
id={i18nId}
defaultMessage={defaultMessage}
style={style.label}
+ testID={`user_status.label.${status}`}
/>
);
}
diff --git a/app/components/sidebars/settings/user_info/user_info.js b/app/components/sidebars/settings/user_info/user_info.js
index 3aaa8ef99..20388bf7e 100644
--- a/app/components/sidebars/settings/user_info/user_info.js
+++ b/app/components/sidebars/settings/user_info/user_info.js
@@ -47,6 +47,7 @@ export default class UserInfo extends PureComponent {
iconSize={28}
showStatus={false}
userId={user.id}
+ testID={`${testID}.profile_picture`}
/>
diff --git a/app/components/user_status/__snapshots__/user_status.test.js.snap b/app/components/user_status/__snapshots__/user_status.test.js.snap
index 4a4579b8e..148cb501f 100644
--- a/app/components/user_status/__snapshots__/user_status.test.js.snap
+++ b/app/components/user_status/__snapshots__/user_status.test.js.snap
@@ -9,6 +9,7 @@ exports[`UserStatus should match snapshot, away status 1`] = `
"fontSize": 32,
}
}
+ testID="user_status.icon.away"
/>
`;
@@ -21,6 +22,7 @@ exports[`UserStatus should match snapshot, dnd status 1`] = `
"fontSize": 32,
}
}
+ testID="user_status.icon.dnd"
/>
`;
@@ -33,6 +35,7 @@ exports[`UserStatus should match snapshot, online status 1`] = `
"fontSize": 32,
}
}
+ testID="user_status.icon.online"
/>
`;
@@ -45,5 +48,6 @@ exports[`UserStatus should match snapshot, should default to offline status 1`]
"fontSize": 32,
}
}
+ testID="user_status.icon.offline"
/>
`;
diff --git a/app/components/user_status/user_status.js b/app/components/user_status/user_status.js
index bda29d27f..9a88b3851 100644
--- a/app/components/user_status/user_status.js
+++ b/app/components/user_status/user_status.js
@@ -50,6 +50,7 @@ export default class UserStatus extends PureComponent {
);
}
diff --git a/app/screens/edit_profile/__snapshots__/edit_profile.test.js.snap b/app/screens/edit_profile/__snapshots__/edit_profile.test.js.snap
index f16072b85..2681f5d78 100644
--- a/app/screens/edit_profile/__snapshots__/edit_profile.test.js.snap
+++ b/app/screens/edit_profile/__snapshots__/edit_profile.test.js.snap
@@ -17,6 +17,7 @@ exports[`edit_profile should match snapshot 1`] = `
size={153}
statusBorderWidth={6}
statusSize={36}
+ testID="edit_profile.profile_picture"
/>
`;
diff --git a/app/screens/edit_profile/edit_profile.js b/app/screens/edit_profile/edit_profile.js
index 08a00f5a7..1ffbbffc2 100644
--- a/app/screens/edit_profile/edit_profile.js
+++ b/app/screens/edit_profile/edit_profile.js
@@ -525,6 +525,7 @@ export default class EditProfile extends PureComponent {
edit={!profilePictureDisabled}
imageUri={uri}
profileImageRemove={profileImageRemove}
+ testID='edit_profile.profile_picture'
/>
);
diff --git a/app/screens/notification/notification_icon.tsx b/app/screens/notification/notification_icon.tsx
index 34ad024e1..73bfc88a1 100644
--- a/app/screens/notification/notification_icon.tsx
+++ b/app/screens/notification/notification_icon.tsx
@@ -51,6 +51,7 @@ const NotificationIcon = ({fromWebhook, overrideIconUrl, senderId, useUserIcon}:
userId={user.id}
size={IMAGE_SIZE}
iconSize={24}
+ testID='in_app_notification.profile_picture'
/>
);
} else {
diff --git a/app/screens/search/__snapshots__/recent_item.test.js.snap b/app/screens/search/__snapshots__/recent_item.test.js.snap
index 6c2715d9f..582cb9da4 100644
--- a/app/screens/search/__snapshots__/recent_item.test.js.snap
+++ b/app/screens/search/__snapshots__/recent_item.test.js.snap
@@ -40,6 +40,7 @@ exports[`Search RecentItem should match snapshot and respond to events 1`] = `
"width": 50,
}
}
+ testID="search.recent_item.test.remove.button"
>
-
+
@@ -105,6 +110,7 @@ export default class ClockDisplay extends ClockDisplayBase {
id='mobile.notification_settings.modal_cancel'
defaultMessage='CANCEL'
style={style.modalFooterOption}
+ testID='clock_display_settings.clock_modal_cancel.button'
/>
@@ -116,6 +122,7 @@ export default class ClockDisplay extends ClockDisplayBase {
id='mobile.notification_settings.modal_save'
defaultMessage='SAVE'
style={style.modalFooterOption}
+ testID='clock_display_settings.clock_modal_save.button'
/>
@@ -131,7 +138,10 @@ export default class ClockDisplay extends ClockDisplayBase {
const style = getStyleSheet(theme);
return (
-
+
{this.renderClockDisplayModal(style)}
diff --git a/app/screens/settings/clock_display/clock_display.ios.js b/app/screens/settings/clock_display/clock_display.ios.js
index 7994107b0..f3794b8b5 100644
--- a/app/screens/settings/clock_display/clock_display.ios.js
+++ b/app/screens/settings/clock_display/clock_display.ios.js
@@ -26,6 +26,7 @@ export default class ClockDisplay extends ClockDisplayBase {
@@ -46,6 +47,7 @@ export default class ClockDisplay extends ClockDisplayBase {
actionType='select'
actionValue='false'
selected={newMilitaryTime === 'false'}
+ testID='clock_display_settings.normal_clock.action'
theme={theme}
/>
@@ -60,6 +62,7 @@ export default class ClockDisplay extends ClockDisplayBase {
actionType='select'
actionValue='true'
selected={newMilitaryTime === 'true'}
+ testID='clock_display_settings.military_clock.action'
theme={theme}
/>
diff --git a/app/screens/settings/display_settings/__snapshots__/display_settings.test.js.snap b/app/screens/settings/display_settings/__snapshots__/display_settings.test.js.snap
index 45a182026..3a5d5bb28 100644
--- a/app/screens/settings/display_settings/__snapshots__/display_settings.test.js.snap
+++ b/app/screens/settings/display_settings/__snapshots__/display_settings.test.js.snap
@@ -14,6 +14,7 @@ exports[`DisplaySettings should match snapshot 1`] = `
"flex": 1,
}
}
+ testID="display_settings.screen"
>
);
@@ -124,6 +125,7 @@ export default class DisplaySettings extends PureComponent {
@@ -137,6 +139,7 @@ export default class DisplaySettings extends PureComponent {
onPress={this.goToThemeSettings}
separator={true}
showArrow={showArrow}
+ testID='display_settings.theme.action'
theme={theme}
/>
)}
@@ -147,6 +150,7 @@ export default class DisplaySettings extends PureComponent {
onPress={this.goToClockDisplaySettings}
separator={disableClockDisplaySeparator}
showArrow={showArrow}
+ testID='display_settings.clock_display.action'
theme={theme}
/>
{timezoneOption}
diff --git a/app/screens/user_profile/__snapshots__/user_profile.test.js.snap b/app/screens/user_profile/__snapshots__/user_profile.test.js.snap
index c261ba98e..0b61dc2f3 100644
--- a/app/screens/user_profile/__snapshots__/user_profile.test.js.snap
+++ b/app/screens/user_profile/__snapshots__/user_profile.test.js.snap
@@ -36,6 +36,7 @@ exports[`user_profile should match snapshot 1`] = `
size={153}
statusBorderWidth={6}
statusSize={36}
+ testID="user_profile.profile_picture"
userId="4hzdnk6mg7gepe7yze6m3domnc"
/>
{this.getDisplayName()}
{`@${user.username}`}
diff --git a/detox/e2e/support/server_api/user.js b/detox/e2e/support/server_api/user.js
index f44c0a4fe..9f08053ff 100644
--- a/detox/e2e/support/server_api/user.js
+++ b/detox/e2e/support/server_api/user.js
@@ -24,6 +24,7 @@ import {getResponseFromError} from './common';
* See https://api.mattermost.com/#tag/users/paths/~1users~1login/post
* @param {string} user.username - username of a user
* @param {string} user.password - password of a user
+ * @return {Object} returns {user, status} on success or {error, status} on error
*/
export const apiLogin = async (user) => {
try {
@@ -49,6 +50,7 @@ export const apiLogin = async (user) => {
/**
* Login to Mattermost server as sysadmin.
+ * @return {Object} returns {user, status} on success or {error, status} on error
*/
export const apiAdminLogin = () => {
return apiLogin({
@@ -60,6 +62,7 @@ export const apiAdminLogin = () => {
/**
* Logout from the Mattermost server.
* See https://api.mattermost.com/#tag/users/paths/~1users~1logout/post
+ * @return {Object} returns data on success
*/
export const apiLogout = async () => {
const response = await client.post('/api/v4/users/logout');
@@ -73,6 +76,7 @@ export const apiLogout = async () => {
* Create a user.
* See https://api.mattermost.com/#tag/users/paths/~1users/post
* @param {Object} user - user object to be created
+ * @return {Object} returns {user} on success or {error, status} on error
*/
export const apiCreateUser = async ({prefix = 'user', user = null} = {}) => {
try {
@@ -91,6 +95,7 @@ export const apiCreateUser = async ({prefix = 'user', user = null} = {}) => {
/**
* Get user from a current session.
+ * @return {Object} returns {user} on success or {error, status} on error
*/
export const apiGetMe = () => {
return apiGetUserById('me');
@@ -99,7 +104,8 @@ export const apiGetMe = () => {
/**
* Get a user by ID.
* See https://api.mattermost.com/#tag/users/paths/~1users~1{user_id}/get
- * @param {string} userId
+ * @param {string} userId - the user ID
+ * @return {Object} returns {user} on success or {error, status} on error
*/
export const apiGetUserById = async (userId) => {
try {
@@ -114,7 +120,8 @@ export const apiGetUserById = async (userId) => {
/**
* Get a user by username.
* See https://api.mattermost.com/#tag/users/paths/~1users~1username~1{username}/get
- * @param {string} username
+ * @param {string} username - the username
+ * @return {Object} returns {user} on success or {error, status} on error
*/
export const apiGetUserByUsername = async (username) => {
try {
@@ -126,6 +133,35 @@ export const apiGetUserByUsername = async (username) => {
}
};
+/**
+ * Patch user from a current session.
+ * @param {Object} userData - data to partially update a user
+ * @return {Object} returns {user} on success or {error, status} on error
+ */
+export const apiPatchMe = (userData) => {
+ return apiPatchUser('me', userData);
+};
+
+/**
+ * Patch a user.
+ * See https://api.mattermost.com/#tag/users/paths/~1users~1{user_id}~1patch/put
+ * @param {string} userId - the user ID
+ * @param {Object} userData - data to partially update a user
+ * @return {Object} returns {user} on success or {error, status} on error
+ */
+export const apiPatchUser = async (userId, userData) => {
+ try {
+ const response = await client.put(
+ `/api/v4/users/${userId}/patch`,
+ userData,
+ );
+
+ return {user: response.data};
+ } catch (err) {
+ return getResponseFromError(err);
+ }
+};
+
function generateRandomUser(prefix) {
const randomId = getRandomId();
@@ -147,6 +183,8 @@ export const User = {
apiGetMe,
apiGetUserById,
apiGetUserByUsername,
+ apiPatchMe,
+ apiPatchUser,
};
export default User;
diff --git a/detox/e2e/support/ui/component/autocomplete.js b/detox/e2e/support/ui/component/autocomplete.js
index 0e029a2bd..3cafdf5da 100644
--- a/detox/e2e/support/ui/component/autocomplete.js
+++ b/detox/e2e/support/ui/component/autocomplete.js
@@ -3,6 +3,8 @@
class Autocomplete {
testID = {
+ atMentionItemPrefix: 'autocomplete.at_mention.item.',
+ channelMentionItemPrefix: 'autocomplete.channel_mention.item.',
autocomplete: 'autocomplete',
atMentionSuggestionList: 'at_mention_suggestion.list',
channelMentionSuggestionList: 'channel_mention_suggestion.list',
@@ -19,7 +21,11 @@ class Autocomplete {
slashSuggestionList = element(by.id(this.testID.slashSuggestionList));
getAtMentionItem = (userId) => {
- return element(by.id(`autocomplete.at_mention.item.${userId}`));
+ return element(by.id(`${this.testID.atMentionItemPrefix}${userId}`));
+ }
+
+ getChannelMentionItem = (channelId) => {
+ return element(by.id(`${this.testID.channelMentionItemPrefix}${channelId}`));
}
toBeVisible = async (isVisible = true) => {
diff --git a/detox/e2e/support/ui/component/bottom_sheet.js b/detox/e2e/support/ui/component/bottom_sheet.js
index 95cfc4fc0..6589d6066 100644
--- a/detox/e2e/support/ui/component/bottom_sheet.js
+++ b/detox/e2e/support/ui/component/bottom_sheet.js
@@ -9,7 +9,11 @@ class BottomSheet {
// bottom sheet options
archivedChannelsOption = isAndroid() ? element(by.text('Archived Channels')) : element(by.label('Archived Channels')).atIndex(0);
+ awayOption = isAndroid() ? element(by.text('Away')) : element(by.label('Away')).atIndex(0);
cancelOption = isAndroid() ? element(by.text('Cancel')) : element(by.label('Cancel')).atIndex(0);
+ doNotDisturbOption = isAndroid() ? element(by.text('Do Not Disturb')) : element(by.label('Do No Disturb')).atIndex(0);
+ offlineOption = isAndroid() ? element(by.text('Offline')) : element(by.label('Offline')).atIndex(0);
+ onlineOption = isAndroid() ? element(by.text('Online')) : element(by.label('Online')).atIndex(0);
publicChannelsOption = isAndroid() ? element(by.text('Public Channels')) : element(by.label('Public Channels')).atIndex(0);
}
diff --git a/detox/e2e/support/ui/component/index.js b/detox/e2e/support/ui/component/index.js
index ec59e1615..f3208381e 100644
--- a/detox/e2e/support/ui/component/index.js
+++ b/detox/e2e/support/ui/component/index.js
@@ -16,6 +16,7 @@ import Post from './post';
import PostDraft from './post_draft';
import PostList from './post_list';
import PostOptions from './post_options';
+import ProfilePicture from './profile_picture';
import RecentItem from './recent_item';
import SearchBar from './search_bar';
import SendButton from './send_button';
@@ -37,6 +38,7 @@ export {
PostDraft,
PostList,
PostOptions,
+ ProfilePicture,
RecentItem,
SearchBar,
SendButton,
diff --git a/detox/e2e/support/ui/component/post.js b/detox/e2e/support/ui/component/post.js
index c68c634bf..9e987eb08 100644
--- a/detox/e2e/support/ui/component/post.js
+++ b/detox/e2e/support/ui/component/post.js
@@ -1,31 +1,71 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
+import ProfilePicture from './profile_picture';
+
class Post {
testID = {
+ postProfilePicturePrefix: 'post_profile_picture.profile_picture.',
+ message: 'markdown_text',
+ postHeaderDateTime: 'post_header.date_time',
+ postHeaderDisplayName: 'post_header.display_name',
postHeaderReply: 'post_header.reply',
postPreHeaderText: 'post_pre_header.text',
- markdownText: 'markdown_text',
}
- getPost = (postItemSourceTestID, postId, postMessage) => {
- const postTestID = `${postItemSourceTestID}.${postId}`;
- const baseMatcher = by.id(postTestID);
- const postItemMatcher = postMessage ? baseMatcher.withDescendant(by.text(postMessage)) : baseMatcher;
- const postItemHeaderReplyMatcher = by.id(this.testID.postHeaderReply).withAncestor(postItemMatcher);
+ getPost = (postItemSourceTestID, postId, postMessage, postProfileOptions = {}) => {
+ const postItemMatcher = this.getPostItemMatcher(postItemSourceTestID, postId, postMessage);
+ const postItemMessageMatcher = by.id(this.testID.message).withAncestor(postItemMatcher);
const postItemPreHeaderTextMatch = by.id(this.testID.postPreHeaderText).withAncestor(postItemMatcher);
- const postItemMessageMatcher = by.id(this.testID.markdownText).withAncestor(postItemMatcher);
return {
postItem: element(postItemMatcher),
- postItemHeaderReply: element(postItemHeaderReplyMatcher),
- postItemPreHeaderText: element(postItemPreHeaderTextMatch),
postItemMessage: element(postItemMessageMatcher),
+ postItemPreHeaderText: element(postItemPreHeaderTextMatch),
+ ...this.getPostHeader(postItemMatcher),
+ ...this.getPostProfilePicture(postItemMatcher, postProfileOptions),
};
}
+ getPostHeader = (postItemMatcher) => {
+ const postItemHeaderDateTimeMatcher = by.id(this.testID.postHeaderDateTime).withAncestor(postItemMatcher);
+ const postItemHeaderDisplayNameMatcher = by.id(this.testID.postHeaderDisplayName).withAncestor(postItemMatcher);
+ const postItemHeaderReplyMatcher = by.id(this.testID.postHeaderReply).withAncestor(postItemMatcher);
+
+ return {
+ postItemHeaderDateTime: element(postItemHeaderDateTimeMatcher),
+ postItemHeaderDisplayName: element(postItemHeaderDisplayNameMatcher),
+ postItemHeaderReply: element(postItemHeaderReplyMatcher),
+ };
+ }
+
+ getPostItemMatcher = (postItemSourceTestID, postId, postMessage) => {
+ const postTestID = `${postItemSourceTestID}.${postId}`;
+ const baseMatcher = by.id(postTestID);
+ return postMessage ? baseMatcher.withDescendant(by.text(postMessage)) : baseMatcher;
+ }
+
getPostMessage = (postItemSourceTestID) => {
- return element(by.id(this.testID.markdownText).withAncestor(by.id(postItemSourceTestID)));
+ return element(by.id(this.testID.message).withAncestor(by.id(postItemSourceTestID)));
+ }
+
+ getPostProfilePicture = (postItemMatcher, postProfileOptions = {userId: null, userStatus: 'online'}) => {
+ if (Object.keys(postProfileOptions).length === 0 || !postProfileOptions.userId) {
+ return {
+ postItemProfilePicture: null,
+ postItemProfilePictureUserStatus: null,
+ };
+ }
+
+ const profilePictureItemMatcher = ProfilePicture.getProfilePictureItemMatcher(this.testID.postProfilePicturePrefix, postProfileOptions.userId);
+ const profilePictureItemUserStatusMatcher = ProfilePicture.getProfilePictureItemUserStatusMatcher(profilePictureItemMatcher, postProfileOptions.userStatus);
+ const postItemProfilePictureMatcher = profilePictureItemMatcher.withAncestor(postItemMatcher);
+ const postItemProfilePictureUserStatusMatcher = profilePictureItemUserStatusMatcher.withAncestor(postItemMatcher);
+
+ return {
+ postItemProfilePicture: element(postItemProfilePictureMatcher),
+ postItemProfilePictureUserStatus: element(postItemProfilePictureUserStatusMatcher),
+ };
}
}
diff --git a/detox/e2e/support/ui/component/post_list.js b/detox/e2e/support/ui/component/post_list.js
index e02896e0b..fc9d99bc7 100644
--- a/detox/e2e/support/ui/component/post_list.js
+++ b/detox/e2e/support/ui/component/post_list.js
@@ -10,19 +10,27 @@ class PostList {
};
}
- getPost = (postId, postMessage) => {
+ getPost = (postId, postMessage, postProfileOptions = {}) => {
const {
postItem,
+ postItemHeaderDateTime,
+ postItemHeaderDisplayName,
postItemHeaderReply,
- postItemPreHeaderText,
postItemMessage,
- } = Post.getPost(this.testID.postListPostItem, postId, postMessage);
+ postItemPreHeaderText,
+ postItemProfilePicture,
+ postItemProfilePictureUserStatus,
+ } = Post.getPost(this.testID.postListPostItem, postId, postMessage, postProfileOptions);
return {
postListPostItem: postItem,
+ postListPostItemHeaderDateTime: postItemHeaderDateTime,
+ postListPostItemHeaderDisplayName: postItemHeaderDisplayName,
postListPostItemHeaderReply: postItemHeaderReply,
- postListPostItemPreHeaderText: postItemPreHeaderText,
postListPostItemMessage: postItemMessage,
+ postListPostItemPreHeaderText: postItemPreHeaderText,
+ postListPostItemProfilePicture: postItemProfilePicture,
+ postListPostItemProfilePictureUserStatus: postItemProfilePictureUserStatus,
};
}
diff --git a/detox/e2e/support/ui/component/profile_picture.js b/detox/e2e/support/ui/component/profile_picture.js
new file mode 100644
index 000000000..53ef4b8e9
--- /dev/null
+++ b/detox/e2e/support/ui/component/profile_picture.js
@@ -0,0 +1,21 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+class ProfilePicture {
+ testID = {
+ userStatusIconPrefix: 'user_status.icon.',
+ }
+
+ getProfilePictureItemMatcher = (profilePictureSourcePrefix, userId) => {
+ const profilePictureTestID = `${profilePictureSourcePrefix}${userId}`;
+ return by.id(profilePictureTestID);
+ }
+
+ getProfilePictureItemUserStatusMatcher(profilePictureItemMatcher, userStatus = 'online') {
+ const userStatusIconTestID = `${this.testID.userStatusIconPrefix}${userStatus}`;
+ return by.id(userStatusIconTestID).withAncestor(profilePictureItemMatcher);
+ }
+}
+
+const profilePicture = new ProfilePicture();
+export default profilePicture;
diff --git a/detox/e2e/support/ui/component/recent_item.js b/detox/e2e/support/ui/component/recent_item.js
index c36440a1d..eaf0e170e 100644
--- a/detox/e2e/support/ui/component/recent_item.js
+++ b/detox/e2e/support/ui/component/recent_item.js
@@ -3,11 +3,18 @@
class RecentItem {
testID = {
- recentItemPrefix: 'search.recent_item.',
+ recentSearchItemPrefix: 'search.recent_item.',
+ recentSearchItemRemoveButtonSuffix: '.remove.button',
}
- getRecentItem = (searchTerms) => {
- return element(by.id(`${this.testID.recentItemPrefix}${searchTerms}`));
+ getRecentSearchItem = (searchTerms) => {
+ const recentSearchItemTestID = `${this.testID.recentSearchItemPrefix}${searchTerms}`;
+ const recentSearchItemRemoveButtonTestID = `${recentSearchItemTestID}${this.testID.recentSearchItemRemoveButtonSuffix}`;
+
+ return {
+ recentSearchItem: element(by.id(recentSearchItemTestID)),
+ recentSearchItemRemoveButton: element(by.id(recentSearchItemRemoveButtonTestID)),
+ };
}
}
diff --git a/detox/e2e/support/ui/component/settings_sidebar.js b/detox/e2e/support/ui/component/settings_sidebar.js
index 93b28781d..f618c982a 100644
--- a/detox/e2e/support/ui/component/settings_sidebar.js
+++ b/detox/e2e/support/ui/component/settings_sidebar.js
@@ -1,8 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
+import BottomSheet from './bottom_sheet';
+
class SettingsSidebar {
testID = {
+ userStatusIconPrefix: 'user_status.icon.',
+ userStatusLabelPrefix: 'user_status.label.',
settingsSidebar: 'settings.sidebar',
userInfoAction: 'settings.sidebar.user_info.action',
statusAction: 'settings.sidebar.status.action',
@@ -22,12 +26,55 @@ class SettingsSidebar {
settingsAction = element(by.id(this.testID.settingsAction));
logoutAction = element(by.id(this.testID.logoutAction));
+ getUserStatus(userStatus) {
+ const userStatusIconTestID = `${this.testID.userStatusIconPrefix}${userStatus}`;
+ const userStatusLabelTestID = `${this.testID.userStatusLabelPrefix}${userStatus}`;
+
+ return {
+ userStatusIcon: element(by.id(userStatusIconTestID)),
+ userStatusLabel: element(by.id(userStatusLabelTestID)),
+ };
+ }
+
toBeVisible = async () => {
await expect(this.settingsSidebar).toBeVisible();
return this.settingsSidebar;
}
+ setUserStatusTo = async (userStatus) => {
+ const {
+ awayOption,
+ doNotDisturbOption,
+ offlineOption,
+ onlineOption,
+ } = BottomSheet;
+
+ // # Set user status
+ await this.statusAction.tap();
+ switch (userStatus) {
+ case 'online':
+ await onlineOption.tap();
+ break;
+ case 'away':
+ await awayOption.tap();
+ break;
+ case 'dnd':
+ await doNotDisturbOption.tap();
+ break;
+ case 'offline':
+ await offlineOption.tap();
+ break;
+ default:
+ throw new Error('Not a valid user status option: ' + userStatus);
+ }
+
+ // * Verify user status
+ const {userStatusIcon, userStatusLabel} = this.getUserStatus(userStatus);
+ await expect(userStatusIcon).toBeVisible();
+ await expect(userStatusLabel).toBeVisible();
+ }
+
tapLogoutAction = async () => {
await this.logoutAction.tap();
await expect(this.settingsSidebar).not.toBeVisible();
diff --git a/detox/e2e/support/ui/screen/channel.js b/detox/e2e/support/ui/screen/channel.js
index b7c938c92..65021a983 100644
--- a/detox/e2e/support/ui/screen/channel.js
+++ b/detox/e2e/support/ui/screen/channel.js
@@ -67,16 +67,16 @@ class ChannelScreen {
postList = new PostList(this.testID.channelScreenPrefix);
- getLongPostItem = (postId, text) => {
- return LongPostScreen.getPost(postId, text);
+ getLongPostItem = (postId, text, postProfileOptions = {}) => {
+ return LongPostScreen.getPost(postId, text, postProfileOptions);
}
getLongPostMessage = () => {
return LongPostScreen.getPostMessage();
}
- getPostListPostItem = (postId, text) => {
- return this.postList.getPost(postId, text);
+ getPostListPostItem = (postId, text, postProfileOptions = {}) => {
+ return this.postList.getPost(postId, text, postProfileOptions);
}
getPostMessageAtIndex = (index) => {
diff --git a/detox/e2e/support/ui/screen/clock_display_settings.js b/detox/e2e/support/ui/screen/clock_display_settings.js
new file mode 100644
index 000000000..bf46ae871
--- /dev/null
+++ b/detox/e2e/support/ui/screen/clock_display_settings.js
@@ -0,0 +1,65 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {DisplaySettingsScreen} from '@support/ui/screen';
+import {isAndroid} from '@support/utils';
+
+class ClockDisplaySettingsScreen {
+ testID = {
+ clockDisplaySettingsScreen: 'clock_display_settings.screen',
+ backButton: 'screen.back.button',
+ militaryClockAction: 'clock_display_settings.military_clock.action',
+ normalClockAction: 'clock_display_settings.normal_clock.action',
+
+ // ANDROID ONLY
+ clockModal: 'clock_display_settings.clock.modal',
+ clockModalCancelButton: 'clock_display_settings.clock_modal_cancel.button',
+ clockModalSaveButton: 'clock_display_settings.clock_modal_save.button',
+ }
+
+ clockDisplaySettingsScreen = element(by.id(this.testID.clockDisplaySettingsScreen));
+ backButton = element(by.id(this.testID.backButton));
+ militaryClockAction = element(by.id(this.testID.militaryClockAction));
+ normalClockAction = element(by.id(this.testID.normalClockAction));
+
+ // ANDROID ONLY
+ clockModal = element(by.id(this.testID.clockModal));
+ clockModalCancelButton = element(by.id(this.testID.clockModalCancelButton));
+ clockModalSaveButton = element(by.id(this.testID.clockModalSaveButton));
+
+ getClockActionFor = (clockKey) => {
+ switch (clockKey) {
+ case 'military':
+ return this.militaryClockAction;
+ case 'normal':
+ return this.normalClockAction;
+ default:
+ throw new Error('Not a valid clock option: ' + clockKey);
+ }
+ }
+
+ toBeVisible = async () => {
+ if (isAndroid()) {
+ await expect(this.clockModal).toBeVisible();
+ return this.clockModal;
+ }
+
+ await expect(this.clockDisplaySettingsScreen).toBeVisible();
+ return this.clockDisplaySettingsScreen;
+ }
+
+ open = async () => {
+ // # Open clock display settings screen
+ await DisplaySettingsScreen.clockDisplayAction.tap();
+
+ return this.toBeVisible();
+ }
+
+ back = async () => {
+ await this.backButton.tap();
+ await expect(this.clockDisplaySettingsScreen).not.toBeVisible();
+ }
+}
+
+const clockDisplaySettingsScreen = new ClockDisplaySettingsScreen();
+export default clockDisplaySettingsScreen;
diff --git a/detox/e2e/support/ui/screen/display_settings.js b/detox/e2e/support/ui/screen/display_settings.js
new file mode 100644
index 000000000..05d571704
--- /dev/null
+++ b/detox/e2e/support/ui/screen/display_settings.js
@@ -0,0 +1,41 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {GeneralSettingsScreen} from '@support/ui/screen';
+
+class DisplaySettingsScreen {
+ testID = {
+ displaySettingsScreen: 'display_settings.screen',
+ backButton: 'screen.back.button',
+ clockDisplayAction: 'display_settings.clock_display.action',
+ themeAction: 'display_settings.theme.action',
+ timezoneAction: 'display_settings.timezone.action',
+ }
+
+ displaySettingsScreen = element(by.id(this.testID.displaySettingsScreen));
+ backButton = element(by.id(this.testID.backButton));
+ clockDisplayAction = element(by.id(this.testID.clockDisplayAction));
+ themeAction = element(by.id(this.testID.themeAction));
+ timezoneAction = element(by.id(this.testID.timezoneAction));
+
+ toBeVisible = async () => {
+ await expect(this.displaySettingsScreen).toBeVisible();
+
+ return this.displaySettingsScreen;
+ }
+
+ open = async () => {
+ // # Open display settings screen
+ await GeneralSettingsScreen.displayAction.tap();
+
+ return this.toBeVisible();
+ }
+
+ back = async () => {
+ await this.backButton.tap();
+ await expect(this.displaySettingsScreen).not.toBeVisible();
+ }
+}
+
+const displaySettingsScreen = new DisplaySettingsScreen();
+export default displaySettingsScreen;
diff --git a/detox/e2e/support/ui/screen/index.js b/detox/e2e/support/ui/screen/index.js
index c156642e3..c5b9a4fb3 100644
--- a/detox/e2e/support/ui/screen/index.js
+++ b/detox/e2e/support/ui/screen/index.js
@@ -5,7 +5,9 @@ import AddReactionScreen from './add_reaction';
import ChannelInfoScreen from './channel_info';
import ChannelNotificationPreferenceScreen from './channel_notification_preference';
import ChannelScreen from './channel';
+import ClockDisplaySettingsScreen from './clock_display_settings';
import CreateChannelScreen from './create_channel';
+import DisplaySettingsScreen from './display_settings';
import EditChannelScreen from './edit_channel';
import EditPostScreen from './edit_post';
import GeneralSettingsScreen from './general_settings';
@@ -32,7 +34,9 @@ export {
ChannelInfoScreen,
ChannelNotificationPreferenceScreen,
ChannelScreen,
+ ClockDisplaySettingsScreen,
CreateChannelScreen,
+ DisplaySettingsScreen,
EditChannelScreen,
EditPostScreen,
GeneralSettingsScreen,
diff --git a/detox/e2e/support/ui/screen/long_post.js b/detox/e2e/support/ui/screen/long_post.js
index 2fc203402..6ef87983d 100644
--- a/detox/e2e/support/ui/screen/long_post.js
+++ b/detox/e2e/support/ui/screen/long_post.js
@@ -8,12 +8,25 @@ class LongPostScreen {
longPostItem: 'long_post.post',
}
- getPost = (postId, postMessage) => {
- const {postItem, postItemHeaderReply, postItemMessage} = Post.getPost(this.testID.longPostItem, postId, postMessage);
+ getPost = (postId, postMessage, postProfileOptions = {}) => {
+ const {
+ postItem,
+ postItemHeaderDateTime,
+ postItemHeaderDisplayName,
+ postItemHeaderReply,
+ postItemMessage,
+ postItemProfilePicture,
+ postItemProfilePictureUserStatus,
+ } = Post.getPost(this.testID.longPostItem, postId, postMessage, postProfileOptions);
+
return {
longPostItem: postItem,
+ longPostItemHeaderDateTime: postItemHeaderDateTime,
+ longPostItemHeaderDisplayName: postItemHeaderDisplayName,
longPostItemHeaderReply: postItemHeaderReply,
longPostItemMessage: postItemMessage,
+ longPostItemProfilePicture: postItemProfilePicture,
+ longPostItemProfilePictureUserStatus: postItemProfilePictureUserStatus,
};
}
diff --git a/detox/e2e/support/ui/screen/more_channels.js b/detox/e2e/support/ui/screen/more_channels.js
index 51c96d988..64e77feb3 100644
--- a/detox/e2e/support/ui/screen/more_channels.js
+++ b/detox/e2e/support/ui/screen/more_channels.js
@@ -9,8 +9,8 @@ import {
class MoreChannelsScreen {
testID = {
- moreChannelsScreen: 'more_channels.screen',
moreChannelsScreenPrefix: 'more_channels.',
+ moreChannelsScreen: 'more_channels.screen',
closeMoreChannelsButton: 'close.more_channels.button',
createButton: 'more_channels.create.button',
channelDropdown: 'more_channels.channel.dropdown',
diff --git a/detox/e2e/support/ui/screen/more_direct_messages.js b/detox/e2e/support/ui/screen/more_direct_messages.js
index 55c3d0059..8adec04aa 100644
--- a/detox/e2e/support/ui/screen/more_direct_messages.js
+++ b/detox/e2e/support/ui/screen/more_direct_messages.js
@@ -8,8 +8,8 @@ import {
class MoreDirectMessagesScreen {
testID = {
+ moreDirectMessagesScreenPrefix: 'more_direct_messages.',
moreDirectMessagesScreen: 'more_direct_messages.screen',
- moreDirectMessagesPrefix: 'more_direct_messages.',
startButton: 'more_direct_messages.start.button',
usersList: 'more_direct_messages.custom_list',
userItem: 'more_direct_messages.custom_list.user_item',
@@ -21,10 +21,10 @@ class MoreDirectMessagesScreen {
usersList = element(by.id(this.testID.usersList));
// convenience props
- searchBar = SearchBar.getSearchBar(this.testID.moreDirectMessagesPrefix);
- searchInput = SearchBar.getSearchInput(this.testID.moreDirectMessagesPrefix);
- cancelButton = SearchBar.getCancelButton(this.testID.moreDirectMessagesPrefix);
- clearButton = SearchBar.getClearButton(this.testID.moreDirectMessagesPrefix);
+ searchBar = SearchBar.getSearchBar(this.testID.moreDirectMessagesScreenPrefix);
+ searchInput = SearchBar.getSearchInput(this.testID.moreDirectMessagesScreenPrefix);
+ cancelButton = SearchBar.getCancelButton(this.testID.moreDirectMessagesScreenPrefix);
+ clearButton = SearchBar.getClearButton(this.testID.moreDirectMessagesScreenPrefix);
getUser = (userId, diplayUsername) => {
const userItemTestID = `${this.testID.userItem}.${userId}`;
diff --git a/detox/e2e/support/ui/screen/permalink.js b/detox/e2e/support/ui/screen/permalink.js
index cbf4cce73..0fbdc41a7 100644
--- a/detox/e2e/support/ui/screen/permalink.js
+++ b/detox/e2e/support/ui/screen/permalink.js
@@ -16,16 +16,16 @@ class PermalinkScreen {
postList = new PostList(this.testID.permalinkScreenPrefix);
- getLongPostItem = (postId, text) => {
- return LongPostScreen.getPost(postId, text);
+ getLongPostItem = (postId, text, postProfileOptions = {}) => {
+ return LongPostScreen.getPost(postId, text, postProfileOptions);
}
getLongPostMessage = () => {
return LongPostScreen.getPostMessage();
}
- getPostListPostItem = (postId, text) => {
- return this.postList.getPost(postId, text);
+ getPostListPostItem = (postId, text, postProfileOptions = {}) => {
+ return this.postList.getPost(postId, text, postProfileOptions);
}
getPostMessageAtIndex = (index) => {
diff --git a/detox/e2e/support/ui/screen/pinned_messages.js b/detox/e2e/support/ui/screen/pinned_messages.js
index e83af8ebd..4d4150163 100644
--- a/detox/e2e/support/ui/screen/pinned_messages.js
+++ b/detox/e2e/support/ui/screen/pinned_messages.js
@@ -15,8 +15,8 @@ class PinnedMessagesScreen {
pinnedMessagesScreen = element(by.id(this.testID.pinnedMessagesScreen));
backButton = element(by.id(this.testID.backButton));
- getSearchResultPostItem = (postId, text) => {
- return SearchResultPostScreen.getPost(postId, text);
+ getSearchResultPostItem = (postId, text, postProfileOptions = {}) => {
+ return SearchResultPostScreen.getPost(postId, text, postProfileOptions);
}
toBeVisible = async () => {
diff --git a/detox/e2e/support/ui/screen/recent_mentions.js b/detox/e2e/support/ui/screen/recent_mentions.js
index c49ab5e7f..84332eb7a 100644
--- a/detox/e2e/support/ui/screen/recent_mentions.js
+++ b/detox/e2e/support/ui/screen/recent_mentions.js
@@ -16,8 +16,8 @@ class RecentMentionsScreen {
recentMentionsScreen = element(by.id(this.testID.recentMentionsScreen));
closeSettingsButton = element(by.id(this.testID.closeSettingsButton));
- getSearchResultPostItem = (postId, text) => {
- return SearchResultPostScreen.getPost(postId, text);
+ getSearchResultPostItem = (postId, text, postProfileOptions = {}) => {
+ return SearchResultPostScreen.getPost(postId, text, postProfileOptions);
}
toBeVisible = async () => {
diff --git a/detox/e2e/support/ui/screen/saved_messages.js b/detox/e2e/support/ui/screen/saved_messages.js
index b80afeff8..6cf329d0b 100644
--- a/detox/e2e/support/ui/screen/saved_messages.js
+++ b/detox/e2e/support/ui/screen/saved_messages.js
@@ -16,8 +16,8 @@ class SavedMessagesScreen {
savedMessagesScreen = element(by.id(this.testID.savedMessagesScreen));
closeSettingsButton = element(by.id(this.testID.closeSettingsButton));
- getSearchResultPostItem = (postId, text) => {
- return SearchResultPostScreen.getPost(postId, text);
+ getSearchResultPostItem = (postId, text, postProfileOptions = {}) => {
+ return SearchResultPostScreen.getPost(postId, text, postProfileOptions);
}
toBeVisible = async () => {
diff --git a/detox/e2e/support/ui/screen/search.js b/detox/e2e/support/ui/screen/search.js
index aa3f36df8..9169188ab 100644
--- a/detox/e2e/support/ui/screen/search.js
+++ b/detox/e2e/support/ui/screen/search.js
@@ -41,11 +41,11 @@ class SearchScreen {
clearButton = SearchBar.getClearButton(this.testID.searchScreenPrefix);
getRecentSearchItem = (searchTerms) => {
- return RecentItem.getRecentItem(searchTerms);
+ return RecentItem.getRecentSearchItem(searchTerms);
}
- getSearchResultPostItem = (postId, text) => {
- return SearchResultPostScreen.getPost(postId, text);
+ getSearchResultPostItem = (postId, text, postProfileOptions = {}) => {
+ return SearchResultPostScreen.getPost(postId, text, postProfileOptions);
}
getSearchResultPostMessageAtIndex = (index) => {
diff --git a/detox/e2e/support/ui/screen/search_result_post.js b/detox/e2e/support/ui/screen/search_result_post.js
index e07b42ac5..301ac3979 100644
--- a/detox/e2e/support/ui/screen/search_result_post.js
+++ b/detox/e2e/support/ui/screen/search_result_post.js
@@ -8,12 +8,25 @@ class SearchResultPostScreen {
searchResultPostItem: 'search_result_post.post',
}
- getPost = (postId, postMessage) => {
- const {postItem, postItemHeaderReply, postItemMessage} = Post.getPost(this.testID.searchResultPostItem, postId, postMessage);
+ getPost = (postId, postMessage, postProfileOptions = {}) => {
+ const {
+ postItem,
+ postItemHeaderDateTime,
+ postItemHeaderDisplayName,
+ postItemHeaderReply,
+ postItemMessage,
+ postItemProfilePicture,
+ postItemProfilePictureUserStatus,
+ } = Post.getPost(this.testID.searchResultPostItem, postId, postMessage, postProfileOptions);
+
return {
searchResultPostItem: postItem,
+ searchResultPostItemHeaderDateTime: postItemHeaderDateTime,
+ searchResultPostItemHeaderDisplayName: postItemHeaderDisplayName,
searchResultPostItemHeaderReply: postItemHeaderReply,
searchResultPostItemMessage: postItemMessage,
+ searchResultPostItemProfilePicture: postItemProfilePicture,
+ searchResultPostItemProfilePictureUserStatus: postItemProfilePictureUserStatus,
};
}
diff --git a/detox/e2e/support/ui/screen/thread.js b/detox/e2e/support/ui/screen/thread.js
index 7458a4278..5010cc010 100644
--- a/detox/e2e/support/ui/screen/thread.js
+++ b/detox/e2e/support/ui/screen/thread.js
@@ -44,16 +44,16 @@ class ThreadScreen {
postList = new PostList(this.testID.threadScreenPrefix);
- getLongPostItem = (postId, text) => {
- return LongPostScreen.getPost(postId, text);
+ getLongPostItem = (postId, text, postProfileOptions = {}) => {
+ return LongPostScreen.getPost(postId, text, postProfileOptions);
}
getLongPostMessage = () => {
return LongPostScreen.getPostMessage();
}
- getPostListPostItem = (postId, text) => {
- return this.postList.getPost(postId, text);
+ getPostListPostItem = (postId, text, postProfileOptions = {}) => {
+ return this.postList.getPost(postId, text, postProfileOptions);
}
getPostMessageAtIndex = (index) => {
diff --git a/detox/e2e/test/smoke_test/clock_display.e2e.js b/detox/e2e/test/smoke_test/clock_display.e2e.js
new file mode 100644
index 000000000..077945176
--- /dev/null
+++ b/detox/e2e/test/smoke_test/clock_display.e2e.js
@@ -0,0 +1,112 @@
+// 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 moment from 'moment-timezone';
+
+import {
+ ChannelScreen,
+ ClockDisplaySettingsScreen,
+ DisplaySettingsScreen,
+ GeneralSettingsScreen,
+} from '@support/ui/screen';
+import {
+ Channel,
+ Post,
+ Setup,
+ System,
+ User,
+} from '@support/server_api';
+import {isAndroid, isIos} from '@support/utils';
+
+describe('Clock Display', () => {
+ const testDate = Date.UTC(new Date().getFullYear() + 1, 0, 5, 14, 37); // Jan 5, 2:37pm
+ const testTimezone = 'UTC';
+ const testFullDate = moment(testDate).tz(testTimezone).format();
+ const testMilitaryTime = moment(testDate).tz(testTimezone).format('HH:mm');
+ const testNormalTime = moment(testDate).tz(testTimezone).format('h:mm A');
+ const testMessage = `Hello from ${testFullDate}, ${testTimezone}`;
+ let testChannel;
+
+ beforeAll(async () => {
+ // # Enable timezone
+ await System.apiUpdateConfig({DisplaySettings: {ExperimentalTimezone: true}});
+
+ const {team, user} = await Setup.apiInit();
+ ({channel: testChannel} = await Channel.apiGetChannelByName(team.name, 'town-square'));
+
+ // # Set user's timezone
+ await User.apiPatchUser(user.id, {timezone: {automaticTimezone: '', manualTimezone: testTimezone, useAutomaticTimezone: 'false'}});
+
+ // # Post message with a future date as sysadmin
+ await User.apiAdminLogin();
+ await Post.apiCreatePost({
+ channelId: testChannel.id,
+ message: testMessage,
+ createAt: testDate,
+ });
+
+ // # Open channel screen
+ await ChannelScreen.open(user);
+ });
+
+ afterAll(async () => {
+ await ChannelScreen.logout();
+ });
+
+ it('MM-T3257 should be able to set clock display to 12-hour', async () => {
+ // # Set clock display to normal (12-hour)
+ await setClockDisplayTo('normal');
+
+ // * Verify clock display is normal
+ await verifyClockDisplay(testChannel, testMessage, testNormalTime);
+ });
+
+ it('MM-T3258 should be able to set clock display to 24-hour', async () => {
+ // # Set clock display to military (24-hour)
+ await setClockDisplayTo('military');
+
+ // * Verify clock display is military
+ await verifyClockDisplay(testChannel, testMessage, testMilitaryTime);
+ });
+});
+
+async function setClockDisplayTo(clockKey) {
+ const {
+ clockModalSaveButton,
+ getClockActionFor,
+ } = ClockDisplaySettingsScreen;
+
+ // # Open clock display settings screen
+ await ChannelScreen.openSettingsSidebar();
+ await GeneralSettingsScreen.open();
+ await DisplaySettingsScreen.open();
+ await ClockDisplaySettingsScreen.open();
+
+ // # Tap on clock option
+ await getClockActionFor(clockKey).tap();
+
+ // # Tap on Save button if Android
+ if (isAndroid()) {
+ await clockModalSaveButton.tap();
+ }
+
+ // # Go back to channel
+ await ClockDisplaySettingsScreen.back();
+ if (isIos()) {
+ await DisplaySettingsScreen.back();
+ }
+ await GeneralSettingsScreen.close();
+}
+
+async function verifyClockDisplay(testChannel, testMessage, clockDisplay) {
+ // * Verify clock display
+ const {post} = await Post.apiGetLastPostInChannel(testChannel.id);
+ const {postListPostItemHeaderDateTime} = await ChannelScreen.getPostListPostItem(post.id, testMessage);
+ await expect(postListPostItemHeaderDateTime).toHaveText(clockDisplay);
+}
diff --git a/detox/e2e/test/smoke_test/email_notifications.e2e.js b/detox/e2e/test/smoke_test/email_notifications.e2e.js
index 33d39bc25..2ebe93ab9 100644
--- a/detox/e2e/test/smoke_test/email_notifications.e2e.js
+++ b/detox/e2e/test/smoke_test/email_notifications.e2e.js
@@ -77,11 +77,11 @@ describe('Email Notifications', () => {
await navigateBackToChannel();
// # Post an at-mention message to mentioned user by other user
- const message = `Mention @${testUser.username} by ${testOtherUser1.username}`;
+ const testMessage = `Mention @${testUser.username} by ${testOtherUser1.username}`;
await User.apiLogin(testOtherUser1);
await Post.apiCreatePost({
channelId: testChannel.id,
- message,
+ message: testMessage,
});
// * Verify mentioned user receives email notification
@@ -93,7 +93,7 @@ describe('Email Notifications', () => {
testChannel.display_name,
testUser,
testOtherUser1,
- message,
+ testMessage,
testConfig.EmailSettings.FeedbackEmail,
testConfig.SupportSettings.SupportEmail);
});
@@ -108,17 +108,17 @@ describe('Email Notifications', () => {
await navigateBackToChannel();
// # Post an at-mention message to mentioned user by other user
- const message = `Mention @${testUser.username} by ${testOtherUser2.username}`;
+ const testMessage = `Mention @${testUser.username} by ${testOtherUser2.username}`;
await User.apiLogin(testOtherUser2);
await Post.apiCreatePost({
channelId: testChannel.id,
- message,
+ message: testMessage,
});
// * Verify mentioned user does not receive email notification
const {data} = await getRecentEmail(testUser.username);
const bodyText = splitEmailBodyText(data.body.text);
- jestExpect(bodyText[7]).not.toEqual(message);
+ jestExpect(bodyText[7]).not.toEqual(testMessage);
});
});
diff --git a/detox/e2e/test/smoke_test/search.e2e.js b/detox/e2e/test/smoke_test/search.e2e.js
index e393793d9..74b66b369 100644
--- a/detox/e2e/test/smoke_test/search.e2e.js
+++ b/detox/e2e/test/smoke_test/search.e2e.js
@@ -21,11 +21,15 @@ import {
} from '@support/server_api';
describe('Search', () => {
+ const testMessage = `test ${Date.now().toString()}`;
+ const partialSearchTerm = testMessage.split('-')[0];
+ const {
+ atMentionSuggestionList,
+ channelMentionSuggestionList,
+ } = Autocomplete;
let testUser;
let testChannel;
- const {atMentionSuggestionList} = Autocomplete;
-
beforeAll(async () => {
const {team, user} = await Setup.apiInit();
testUser = user;
@@ -41,11 +45,33 @@ describe('Search', () => {
await ChannelScreen.logout();
});
- it('MM-T3236 search on sender, select from autocomplete, reply from search results', async () => {
- const testMessage = Date.now().toString();
+ it('MM-T3235 search on text, jump to result', async () => {
+ // # Post message and search on text
+ await postMessageAndSearchText(testMessage, partialSearchTerm);
+ // # Open permalink from search result post item
+ const lastPost = await Post.apiGetLastPostInChannel(testChannel.id);
+ const {searchResultPostItem} = await SearchScreen.getSearchResultPostItem(lastPost.post.id, testMessage);
+ await expect(searchResultPostItem).toBeVisible();
+ await searchResultPostItem.tap();
+
+ // * Verify permalink post list has the message
+ await PermalinkScreen.toBeVisible();
+ const {postListPostItem: permalinkPostItem} = await PermalinkScreen.getPostListPostItem(lastPost.post.id, testMessage);
+ await waitFor(permalinkPostItem).toBeVisible();
+
+ // # Jump to recent messages
+ await PermalinkScreen.jumpToRecentMessages();
+
+ // * Verify user is on channel where message is posted
+ await expect(ChannelScreen.channelNavBarTitle).toHaveText(testChannel.display_name);
+ const {postListPostItem: channelPostItem} = await ChannelScreen.getPostListPostItem(lastPost.post.id, testMessage);
+ await expect(channelPostItem).toBeVisible();
+ });
+
+ it('MM-T3236 search on sender, select from autocomplete, reply from search results', async () => {
// # Post message and search on sender
- await postMessageAndSearchFrom(testMessage, testUser, atMentionSuggestionList);
+ await postMessageAndSearchFrom(testMessage, testUser, atMentionSuggestionList, partialSearchTerm);
// * Verify search result post has the message
const lastPost = await Post.apiGetLastPostInChannel(testChannel.id);
@@ -73,35 +99,53 @@ describe('Search', () => {
await SearchScreen.cancel();
});
- it('MM-T3235 search on text, jump to result', async () => {
- const testMessage = Date.now().toString();
+ it('MM-T3237 search on channel, select from autocomplete, search in combination with a text string as well', async () => {
+ // # Post message and search in channel
+ await postMessageAndSearchIn(testMessage, testChannel, channelMentionSuggestionList, partialSearchTerm);
- // # Post message and search on text
- await postMessageAndSearchText(testMessage);
-
- // # Open permalink from search result post item
+ // * Verify search result post has the message
const lastPost = await Post.apiGetLastPostInChannel(testChannel.id);
const {searchResultPostItem} = await SearchScreen.getSearchResultPostItem(lastPost.post.id, testMessage);
await expect(searchResultPostItem).toBeVisible();
- await searchResultPostItem.tap();
- // * Verify permalink post list has the message
- await PermalinkScreen.toBeVisible();
- const {postListPostItem: permalinkPostItem} = await PermalinkScreen.getPostListPostItem(lastPost.post.id, testMessage);
- await waitFor(permalinkPostItem).toBeVisible();
+ // # Go back to channel
+ await SearchScreen.cancel();
+ });
- // # Jump to recent messages
- await PermalinkScreen.jumpToRecentMessages();
+ it('MM-T3238 delete one previous search, tap on another', async () => {
+ const {
+ getRecentSearchItem,
+ searchInModifier,
+ } = SearchScreen;
- // * Verify user is on channel where message is posted
- await expect(ChannelScreen.channelNavBarTitle).toHaveText(testChannel.display_name);
- const {postListPostItem: channelPostItem} = await ChannelScreen.getPostListPostItem(lastPost.post.id, testMessage);
- await expect(channelPostItem).toBeVisible();
+ // # Open search screen
+ await SearchScreen.open();
+
+ // # Delete one previous text search
+ const previousTextSearch = await getRecentSearchItem(testMessage);
+ await previousTextSearch.recentSearchItemRemoveButton.tap();
+
+ // * Verify previous text search is removed
+ await expect(previousTextSearch.recentSearchItem).not.toBeVisible();
+
+ // # Tap on previous in search
+ const inSearchTerms = `${searchInModifier} ${testChannel.name} ${partialSearchTerm}`;
+ const previousInSearch = await getRecentSearchItem(inSearchTerms);
+ await previousInSearch.recentSearchItem.tap();
+
+ // * Verify search result post has the message
+ const lastPost = await Post.apiGetLastPostInChannel(testChannel.id);
+ const {searchResultPostItem} = await SearchScreen.getSearchResultPostItem(lastPost.post.id, testMessage);
+ await expect(searchResultPostItem).toBeVisible();
+
+ // # Go back to channel
+ await SearchScreen.cancel();
});
});
-async function postMessageAndSearchFrom(testMessage, testUser, atMentionSuggestionList) {
+async function postMessageAndSearchFrom(testMessage, testUser, atMentionSuggestionList, partialSearchTerm) {
const {
+ getRecentSearchItem,
searchFromModifier,
searchFromSection,
searchInput,
@@ -126,18 +170,64 @@ async function postMessageAndSearchFrom(testMessage, testUser, atMentionSuggesti
const userAtMentionAutocomplete = await Autocomplete.getAtMentionItem(testUser.id);
await userAtMentionAutocomplete.tap();
+ // # Type end of search term
+ await searchInput.typeText(partialSearchTerm);
+
// # Search user
await searchInput.tapReturnKey();
await expect(atMentionSuggestionList).not.toExist();
// * Verify recent search item is displayed
- const searchTerms = `${searchFromModifier} ${testUser.username}`;
- const recentSearchItem = await SearchScreen.getRecentSearchItem(searchTerms);
+ const searchTerms = `${searchFromModifier} ${testUser.username} ${partialSearchTerm}`;
+ const {recentSearchItem} = await getRecentSearchItem(searchTerms);
await expect(recentSearchItem).toBeVisible();
}
-async function postMessageAndSearchText(testMessage) {
- const {searchInput} = SearchScreen;
+async function postMessageAndSearchIn(testMessage, testChannel, channelMentionSuggestionList, partialSearchTerm) {
+ const {
+ getRecentSearchItem,
+ searchInModifier,
+ searchInSection,
+ searchInput,
+ } = SearchScreen;
+
+ // # Post a message
+ await ChannelScreen.postMessage(testMessage);
+
+ // # Open search screen
+ await SearchScreen.open();
+
+ // # Tap "in:" modifier
+ await searchInput.clearText();
+ await searchInSection.tap();
+
+ // # Type beginning of search term
+ const searchTerm = testChannel.name;
+ await searchInput.typeText(searchTerm);
+
+ // # Select channel from autocomplete
+ await expect(channelMentionSuggestionList).toExist();
+ const channelMentionAutocomplete = await Autocomplete.getChannelMentionItem(testChannel.id);
+ await channelMentionAutocomplete.tap();
+
+ // # Type end of search term
+ await searchInput.typeText(partialSearchTerm);
+
+ // # Search channel
+ await searchInput.tapReturnKey();
+ await expect(channelMentionSuggestionList).not.toExist();
+
+ // * Verify recent search item is displayed
+ const searchTerms = `${searchInModifier} ${testChannel.name} ${partialSearchTerm}`;
+ const {recentSearchItem} = await getRecentSearchItem(searchTerms);
+ await expect(recentSearchItem).toBeVisible();
+}
+
+async function postMessageAndSearchText(testMessage, partialSearchTerm) {
+ const {
+ getRecentSearchItem,
+ searchInput,
+ } = SearchScreen;
// # Post a message
await ChannelScreen.postMessage(testMessage);
@@ -147,12 +237,12 @@ async function postMessageAndSearchText(testMessage) {
// # Type beginning of search term
await searchInput.clearText();
- await searchInput.typeText(testMessage);
+ await searchInput.typeText(partialSearchTerm);
// # Search text
await searchInput.tapReturnKey();
// * Verify recent search item is displayed
- const recentSearchItem = await SearchScreen.getRecentSearchItem(testMessage);
+ const {recentSearchItem} = await getRecentSearchItem(testMessage);
await expect(recentSearchItem).toBeVisible();
}
diff --git a/detox/e2e/test/smoke_test/user_status.e2e.js b/detox/e2e/test/smoke_test/user_status.e2e.js
new file mode 100644
index 000000000..c4d22c621
--- /dev/null
+++ b/detox/e2e/test/smoke_test/user_status.e2e.js
@@ -0,0 +1,75 @@
+// 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 {
+ MainSidebar,
+ SettingsSidebar,
+} from '@support/ui/component';
+import {ChannelScreen} from '@support/ui/screen';
+import {
+ Channel,
+ Post,
+ Setup,
+ Team,
+ User,
+} from '@support/server_api';
+
+describe('User Status', () => {
+ const testMessage = Date.now().toString();
+ let testUser;
+ let testOtherUser;
+ let testChannel;
+
+ beforeAll(async () => {
+ const {user, team} = await Setup.apiInit();
+ testUser = user;
+
+ ({user: testOtherUser} = await User.apiCreateUser());
+ await Team.apiAddUserToTeam(testOtherUser.id, team.id);
+
+ ({channel: testChannel} = await Channel.apiCreateDirectChannel([testUser.id, testOtherUser.id]));
+ await User.apiLogin(testUser);
+ await Post.apiCreatePost({
+ channelId: testChannel.id,
+ message: testMessage,
+ });
+
+ // # Open channel screen
+ await ChannelScreen.open(testUser);
+ });
+
+ afterAll(async () => {
+ await ChannelScreen.logout();
+ });
+
+ it('MM-T3251 should be able to set status', async () => {
+ const {
+ closeSettingsSidebar,
+ getPostListPostItem,
+ openMainSidebar,
+ openSettingsSidebar,
+ } = ChannelScreen;
+
+ // # Set user status to away
+ await openSettingsSidebar();
+ await SettingsSidebar.setUserStatusTo('away');
+ await closeSettingsSidebar();
+
+ // # Login as other user
+ await ChannelScreen.logout();
+ await ChannelScreen.open(testOtherUser);
+
+ // * Verify user's profile has status away as seen by other user
+ await openMainSidebar();
+ await MainSidebar.getChannelByDisplayName(testUser.username).tap();
+ const {post} = await Post.apiGetLastPostInChannel(testChannel.id);
+ const {postListPostItemProfilePictureUserStatus} = await getPostListPostItem(post.id, testMessage, {userId: testUser.id, userStatus: 'away'});
+ await expect(postListPostItemProfilePictureUserStatus).toBeVisible();
+ });
+});