diff --git a/app/components/drafts_buttton/drafts_button.tsx b/app/components/drafts_buttton/drafts_button.tsx index 349ba44f1..dd37ca5cf 100644 --- a/app/components/drafts_buttton/drafts_button.tsx +++ b/app/components/drafts_buttton/drafts_button.tsx @@ -119,7 +119,12 @@ const DraftsButton: React.FC = ({ color={theme.sidebarText} style={customStyles.opacity} /> - {draftsCount} + + {draftsCount} + diff --git a/app/components/option_item/index.tsx b/app/components/option_item/index.tsx index 79ed35af5..342d8dbe7 100644 --- a/app/components/option_item/index.tsx +++ b/app/components/option_item/index.tsx @@ -214,7 +214,7 @@ const OptionItem = ({ value={selected} trackColor={trackColor} thumbColor={thumbColor} - testID={`${testID}.toggled.${selected}`} + testID={`${testID}.toggled.${selected}.${value}`} /> ); } else if (type === OptionType.ARROW) { diff --git a/app/components/post_draft/draft_input/header.tsx b/app/components/post_draft/draft_input/header.tsx index db004fe28..f8b7bb456 100644 --- a/app/components/post_draft/draft_input/header.tsx +++ b/app/components/post_draft/draft_input/header.tsx @@ -50,12 +50,14 @@ export default function DraftInputHeader({ {postPriority.requested_ack && ( <> {!postPriority.priority && ( {noMentionsError && ( { labelText = intl.formatMessage({id: 'post_priority.label.important', defaultMessage: 'IMPORTANT'}); } return ( - + {displayPersistentNotifications && ( @@ -211,6 +212,7 @@ const PostPriorityPicker = ({ type='toggle' selected={data.persistent_notifications} descriptionNumberOfLines={2} + value='persistent_notifications' /> )} diff --git a/detox/e2e/support/ui/component/alert.ts b/detox/e2e/support/ui/component/alert.ts index 7cb2a9978..a3f01b588 100644 --- a/detox/e2e/support/ui/component/alert.ts +++ b/detox/e2e/support/ui/component/alert.ts @@ -56,6 +56,7 @@ class Alert { removeButton3 = isAndroid() ? element(by.text('REMOVE')) : element(by.label('Remove')).atIndex(3); yesButton = isAndroid() ? element(by.text('YES')) : element(by.label('Yes')).atIndex(0); yesButton2 = isAndroid() ? element(by.text('YES')) : element(by.label('Yes')).atIndex(1); + sendButton = isAndroid() ? element(by.text('SEND')) : element(by.label('Send')).atIndex(1); } const alert = new Alert(); diff --git a/detox/e2e/support/ui/screen/channel.ts b/detox/e2e/support/ui/screen/channel.ts index 5581de9e9..6aa98ec76 100644 --- a/detox/e2e/support/ui/screen/channel.ts +++ b/detox/e2e/support/ui/screen/channel.ts @@ -42,8 +42,18 @@ class ChannelScreen { introUnfavoriteAction: 'channel_post_list.intro_options.unfavorite.action', introChannelInfoAction: 'channel_post_list.intro_options.channel_info.action', toastMessage: 'toast.message', + postPriorityPicker: 'channel.post_draft.quick_actions.post_priority_action', + postPriorityImportantMessage: 'post_priority_picker_item.important', + postPriorityUrgentMessage: 'post_priority_picker_item.urgent', + postPriorityRequestAck: 'post_priority_picker_item.requested_ack.toggled.false.requested_ack', + postPriorityPersistentNotification: 'post_priority_picker_item.persistent_notifications.toggled.undefined.persistent_notifications', }; + postPriorityPersistentNotification = element(by.id(this.testID.postPriorityPersistentNotification)); + postPriorityUrgentMessage = element(by.id(this.testID.postPriorityUrgentMessage)); + postPriorityRequestAck = element(by.id(this.testID.postPriorityRequestAck)); + postPriorityImportantMessage = element(by.id(this.testID.postPriorityImportantMessage)); + postPriorityPicker = element(by.id(this.testID.postPriorityPicker)); archievedCloseChannelButton = element(by.id(this.testID.archievedCloseChannelButton)); channelScreen = element(by.id(this.testID.channelScreen)); channelQuickActionsButton = element(by.id(this.testID.channelQuickActionsButton)); @@ -63,6 +73,7 @@ class ChannelScreen { introUnfavoriteAction = element(by.id(this.testID.introUnfavoriteAction)); introChannelInfoAction = element(by.id(this.testID.introChannelInfoAction)); toastMessage = element(by.id(this.testID.toastMessage)); + applyPostPriority = element(by.text('Apply')); // convenience props backButton = NavigationHeader.backButton; @@ -196,6 +207,30 @@ class ChannelScreen { this.getPostMessageAtIndex(index), ).toHaveText(postMessage); }; + + openPostPriorityPicker = async () => { + await this.postPriorityPicker.tap(); + }; + + clickPostPriorityImportantMessage = async () => { + await this.postPriorityImportantMessage.tap(); + }; + + clickPostPriorityUrgentMessage = async () => { + await this.postPriorityUrgentMessage.tap(); + }; + + toggleRequestAckPostpriority = async () => { + await this.postPriorityRequestAck.tap(); + }; + + togglePersistentNotificationPostpriority = async () => { + await this.postPriorityPersistentNotification.tap(); + }; + + applyPostPrioritySettings = async () => { + await this.applyPostPriority.tap(); + }; } const channelScreen = new ChannelScreen(); diff --git a/detox/e2e/support/ui/screen/channel_list.ts b/detox/e2e/support/ui/screen/channel_list.ts index d92ba2201..8bf56bd8a 100644 --- a/detox/e2e/support/ui/screen/channel_list.ts +++ b/detox/e2e/support/ui/screen/channel_list.ts @@ -12,6 +12,9 @@ class ChannelListScreen { testID = { categoryHeaderPrefix: 'channel_list.category_header.', categoryPrefix: 'channel_list.category.', + draftChannelInfo: 'draft_post.channel_info', + draftbuttonListScreen: 'channel_list.drafts.button', + draftCountListScreen: 'channel_list.drafts.count', teamItemPrefix: 'team_sidebar.team_list.team_item.', channelListScreen: 'channel_list.screen', serverIcon: 'channel_list.servers.server_icon', @@ -83,6 +86,22 @@ class ChannelListScreen { return this.toBeVisible(); }; + + draftsButton = { + toBeVisible: async () => { + await waitFor(element(by.id(this.testID.draftbuttonListScreen))).toBeVisible().withTimeout(timeouts.ONE_SEC); + }, + toNotBeVisible: async () => { + await waitFor(element(by.id(this.testID.draftbuttonListScreen))).not.toBeVisible().withTimeout(timeouts.ONE_SEC); + }, + tap: async () => { + await element(by.id(this.testID.draftbuttonListScreen)).tap(); + }, + }; + + getDraftChannelInfo = () => { + return element(by.id(this.testID.draftChannelInfo)); + }; } const channelListScreen = new ChannelListScreen(); diff --git a/detox/e2e/support/ui/screen/draft_screen.ts b/detox/e2e/support/ui/screen/draft_screen.ts new file mode 100644 index 000000000..75fc591d3 --- /dev/null +++ b/detox/e2e/support/ui/screen/draft_screen.ts @@ -0,0 +1,90 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Alert, NavigationHeader} from '@support/ui/component'; +import {timeouts, wait} from '@support/utils'; +import {expect} from 'detox'; + +class DraftScreen { + testID = { + editDraft: 'edit_draft', + deleteDraft: 'delete_draft', + draftMessageContent: 'draft_message', + draftScreen: 'global_drafts_list', + draftTooltipCloseButton: 'draft.tooltip.close.button', + draftPost: 'draft_post', + draftSendButton: 'send_draft_button', + draftEmptyTitle: 'drafts.empty.title', + requestACKIcon: 'drafts.requested_ack.icon', + persistentNotificationIcon: 'drafts.persistent_notifications.icon', + }; + + persistentNotificationIcon = element(by.id(this.testID.persistentNotificationIcon)); + requestACKIcon = element(by.id(this.testID.requestACKIcon)); + editDraft = element(by.id(this.testID.editDraft)); + backButton = NavigationHeader.backButton; + draftScreen = element(by.id(this.testID.draftScreen)); + draftPost = element(by.id(this.testID.draftPost)); + draftSendButton = element(by.id(this.testID.draftSendButton)); + draftEmptyTitle = element(by.id(this.testID.draftEmptyTitle)); + deleteDraftSwipeAction = element(by.text('Delete draft')); + draftMessageContent = element(by.id(this.testID.draftMessageContent)); + deleteDraft = element(by.id(this.testID.deleteDraft)); + + draftTooltipCloseButton = { + tap: async () => { + await element(by.id(this.testID.draftTooltipCloseButton)).tap(); + }, + }; + + openDraftPostActions = async () => { + await this.draftPost.longPress(); + }; + + swipeDraftPostLeft = async () => { + await this.draftPost.swipe('left'); + }; + + deleteDraftPost = async (deleteAction: any) => { + await expect(deleteAction).toBeVisible(); + await deleteAction.tap(); + await waitFor(Alert.deleteButton).toExist().withTimeout(timeouts.TEN_SEC); + await Alert.deleteButton.tap(); + }; + + deleteDraftPostFromSwipeActions = async () => { + await this.deleteDraftPost(this.deleteDraftSwipeAction); + }; + + deleteDraftPostFromDraftActions = async () => { + await this.deleteDraftPost(this.deleteDraft); + }; + + sendDraft = async () => { + await this.draftSendButton.tap(); + await waitFor(Alert.sendButton).toExist().withTimeout(timeouts.TEN_SEC); + await Alert.sendButton.tap(); + }; + + editDraftPost = async () => { + await this.editDraft.tap(); + }; + + draftEmptyScreen = async () => { + return this.draftEmptyTitle; + }; + + back = async () => { + await wait(timeouts.ONE_SEC); + await this.backButton.tap(); + expect(this.draftScreen).not.toBeVisible(); + }; + + getDraftMessageContentText = async () => { + await expect(this.draftMessageContent).toBeVisible(); + return this.draftMessageContent; + }; +} + +const draftScreen = new DraftScreen(); +export default draftScreen; diff --git a/detox/e2e/support/ui/screen/index.ts b/detox/e2e/support/ui/screen/index.ts index 99497dda6..1abe58da3 100644 --- a/detox/e2e/support/ui/screen/index.ts +++ b/detox/e2e/support/ui/screen/index.ts @@ -15,6 +15,7 @@ import CreateDirectMessageScreen from './create_direct_message'; import CreateOrEditChannelScreen from './create_or_edit_channel'; import CustomStatusScreen from './custom_status'; import DisplaySettingsScreen from './display_settings'; +import DraftScreen from './draft_screen'; import EditPostScreen from './edit_post'; import EditProfileScreen from './edit_profile'; import EditServerScreen from './edit_server'; @@ -93,4 +94,5 @@ export { ThreadOptionsScreen, TimezoneDisplaySettingsScreen, UserProfileScreen, + DraftScreen, }; diff --git a/detox/e2e/test/messaging/message_local_drafts.e2e.ts b/detox/e2e/test/messaging/message_local_drafts.e2e.ts new file mode 100644 index 000000000..cce86fbf7 --- /dev/null +++ b/detox/e2e/test/messaging/message_local_drafts.e2e.ts @@ -0,0 +1,178 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelScreen, + ChannelListScreen, + DraftScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {getRandomId, isIos} from '@support/utils'; +import {expect} from 'detox'; + +describe('Messaging - Message Draft', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T5637 should be able to send the draft message from Draft screen', async () => { + // # Open a channel screen and create a message draft + const message = `Message ${getRandomId()}`; + await openChannel(channelsCategory, testChannel); + await createDraft(message, testChannel); + await verifyDraftonChannelList('1'); + await ChannelListScreen.draftsButton.tap(); + await DraftScreen.draftTooltipCloseButton.tap(); + await DraftScreen.openDraftPostActions(); + await DraftScreen.sendDraft(); + await DraftScreen.backButton.tap(); + }); + + it('MM-T5638 should be able to swipe left and delete a draft message', async () => { + // # Open a channel screen and create a message draft + const message = `Message ${getRandomId()}`; + await openChannel(channelsCategory, testChannel); + await createDraft(message, testChannel); + await verifyDraftonChannelList('1'); + await ChannelListScreen.draftsButton.tap(); + await DraftScreen.swipeDraftPostLeft(); + await DraftScreen.deleteDraftPostFromSwipeActions(); + await DraftScreen.backButton.tap(); + }); + + it('MM-T5638 should be able to delete a draft message from long press Draft actions', async () => { + // # Open a channel screen and create a message draft + const message = `Message ${getRandomId()}`; + await openChannel(channelsCategory, testChannel); + await createDraft(message, testChannel); + await verifyDraftonChannelList('1'); + await ChannelListScreen.draftsButton.tap(); + await DraftScreen.openDraftPostActions(); + await DraftScreen.deleteDraftPostFromDraftActions(); + await DraftScreen.backButton.tap(); + }); + + it('MM-T5636 should be able to Edit a draft message', async () => { + // # Open a channel screen and create a message draft + const FirstMessage = `Message ${getRandomId()}`; + const SecondMessage = `Message ${getRandomId()}`; + + await openChannel(channelsCategory, testChannel); + await createDraft(FirstMessage, testChannel); + await verifyDraftonChannelList('1'); + await ChannelListScreen.draftsButton.tap(); + await DraftScreen.openDraftPostActions(); + await DraftScreen.editDraftPost(); + await ChannelScreen.postInput.tap(); + await ChannelScreen.postInput.clearText(); + await ChannelScreen.postInput.replaceText(SecondMessage); + await ChannelScreen.back(); + + // # Verify the draft message is updated + await expect(element(by.text(SecondMessage))).toBeVisible(); + await cleanupDrafts(); + await DraftScreen.backButton.tap(); + }); + + it('MM-T5668 should be able to verify drafts tab shows message priority "Important" and request acknowledgement', async () => { + // # Open a channel screen and create a message draft + const message = `Message ${getRandomId()}`; + + await openChannel(channelsCategory, testChannel); + await createDraft(message, testChannel); + + await ChannelScreen.openPostPriorityPicker(); + await ChannelScreen.clickPostPriorityImportantMessage(); + await ChannelScreen.toggleRequestAckPostpriority(); + await ChannelScreen.applyPostPrioritySettings(); + await verifyDraftonChannelList('1'); + await ChannelListScreen.draftsButton.tap(); + await expect(DraftScreen.requestACKIcon).toBeVisible(); + await cleanupDrafts(); + await DraftScreen.backButton.tap(); + }); + + it('MM-T5668 should be able to verify drafts tab shows message priority "Urgent" and persistent notification', async () => { + // # Open a channel screen and create a message draft + const message = `Message ${getRandomId()}`; + + await openChannel(channelsCategory, testChannel); + await createDraft(message, testChannel); + + await ChannelScreen.openPostPriorityPicker(); + await ChannelScreen.clickPostPriorityUrgentMessage(); + await ChannelScreen.togglePersistentNotificationPostpriority(); + await ChannelScreen.applyPostPrioritySettings(); + await verifyDraftonChannelList('1'); + await ChannelListScreen.draftsButton.tap(); + await expect(DraftScreen.persistentNotificationIcon).toBeVisible(); + await DraftScreen.backButton.tap(); + }); +}); + +async function openChannel(channelsCategory: string, testChannel: any) { + await ChannelListScreen.draftsButton.toNotBeVisible(); + await ChannelScreen.open(channelsCategory, testChannel.name); +} + +async function createDraft(message: string, testChannel: any): Promise { + await ChannelScreen.postInput.tap(); + await ChannelScreen.postInput.replaceText(message); + + // * Verify message exists in post draft and is not yet added to post list + if (isIos()) { + await expect(ChannelScreen.postInput).toHaveValue(message); + } else { + await expect(ChannelScreen.postInput).toHaveText(message); + } + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItem).not.toExist(); +} + +async function verifyDraftonChannelList(draftCount: string) { + await ChannelScreen.back(); + await ChannelListScreen.draftsButton.toBeVisible(); + await expect(element(by.id(ChannelListScreen.testID.draftCountListScreen))).toHaveText(draftCount); +} + +async function cleanupDrafts() { + // # Clean up drafts + await DraftScreen.openDraftPostActions(); + await DraftScreen.deleteDraftPostFromDraftActions(); +}