Этот коммит содержится в:
Mario Vitale
2023-03-27 16:28:42 +02:00
родитель da7a6825ce
Коммит ba6b97fb62
1142 изменённых файлов: 44 добавлений и 44 удалений

Просмотреть файл

@@ -0,0 +1,181 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @collapsed_reply_threads
import {spyNotificationAs} from '../../../support/notification';
describe('CRT Desktop notifications', () => {
let testTeam;
let testChannelUrl;
let testChannelId;
let testChannelName;
let receiver;
let sender;
before(() => {
cy.apiUpdateConfig({
ServiceSettings: {
ThreadAutoFollow: true,
CollapsedThreads: 'default_on',
},
});
cy.apiCreateUser().then(({user}) => {
sender = user;
});
cy.apiInitSetup().then(({team, channel, user, channelUrl}) => {
testTeam = team;
receiver = user;
testChannelUrl = channelUrl;
testChannelId = channel.id;
testChannelName = channel.display_name;
cy.apiAddUserToTeam(testTeam.id, sender.id).then(() => {
cy.apiAddUserToChannel(testChannelId, sender.id);
});
// # Login as receiver
cy.apiLogin(receiver);
});
});
it('MM-T4417_1 Trigger notifications on all replies when channel setting is checked', () => {
// # Visit channel
cy.visit(testChannelUrl);
// Setup notification spy
spyNotificationAs('notifySpy', 'granted');
// # Set users notification settings
setCRTDesktopNotification('ALL');
// # Post a root message as other user
cy.postMessageAs({sender, message: 'This is a not followed root message', channelId: testChannelId, rootId: ''}).then(({id: postId}) => {
// # Switch to town-square so that unread notifications in test channel may be triggered
cy.uiClickSidebarItem('town-square');
// # Post a message in unfollowed thread as another user
cy.postMessageAs({sender, message: 'This is a reply to the unfollowed thread', channelId: testChannelId, rootId: postId});
// * Verify stub was not called for unfollowed thread
cy.get('@notifySpy').should('not.be.called');
});
// # Visit channel
cy.visit(testChannelUrl);
// Setup notification spy
spyNotificationAs('notifySpy', 'granted');
// # Post a message
cy.postMessage('Hi there, this is a root message');
// # Get post id of message
cy.getLastPostId().then((postId) => {
// # Switch to town-square so that unread notifications in test channel may be triggered
cy.uiClickSidebarItem('town-square');
// # Post a message in original thread as another user
const message = 'This is a reply to the root post';
cy.postMessageAs({sender, message, channelId: testChannelId, rootId: postId});
// * Verify stub was called with correct title and body
cy.get('@notifySpy').should('have.been.calledWithMatch', `Reply in ${testChannelName}`, (args) => {
expect(args.body, `Notification body: "${args.body}" should match: "${message}"`).to.equal(`@${sender.username}: ${message}`);
return true;
});
});
});
it('MM-T4417_2 Trigger notifications only on mention replies when channel setting is unchecked', () => {
cy.visit(testChannelUrl);
// Setup notification spy
spyNotificationAs('notifySpy', 'granted');
// # Set users notification settings
setCRTDesktopNotification('MENTION');
// # Post a root message as other user
cy.postMessageAs({sender, message: 'This is a not followed root message', channelId: testChannelId, rootId: ''}).then(({id: postId}) => {
// # Switch to town-square so that unread notifications in test channel may be triggered
cy.uiClickSidebarItem('town-square');
// # Post a message in unfollowed thread as another user
cy.postMessageAs({sender, message: 'This is a reply to the unfollowed thread', channelId: testChannelId, rootId: postId});
// * Verify stub was not called for unfollowed thread
cy.get('@notifySpy').should('not.be.called');
});
// # Visit channel
cy.visit(testChannelUrl);
// Setup notification spy
spyNotificationAs('notifySpy', 'granted');
// # Post a message
cy.postMessage('Hi there, this is a root message');
// # Get post id of message
cy.getLastPostId().then((postId) => {
// # Switch to town-square so that unread notifications in test channel may be triggered
cy.uiClickSidebarItem('town-square');
// # Post a message in original thread as another user
cy.postMessageAs({sender, message: 'This is a reply to the root post', channelId: testChannelId, rootId: postId});
// * Verify stub was not called
cy.get('@notifySpy').should('not.be.called');
// # Post a mention message in original thread as another user
const message = `@${receiver.username} this is a mention to receiver`;
cy.postMessageAs({sender, message, channelId: testChannelId, rootId: postId});
// * Verify stub was called with correct title and body
cy.get('@notifySpy').should('have.been.calledWithMatch', `Reply in ${testChannelName}`, (args) => {
expect(args.body, `Notification body: "${args.body}" should match: "${message}"`).to.equal(`@${sender.username}: ${message}`);
return true;
});
});
});
});
function setCRTDesktopNotification(type) {
if (['ALL', 'MENTION'].indexOf(type) === -1) {
throw new Error(`${type} is invalid`);
}
// # Open settings modal
cy.uiOpenChannelMenu('Notification Preferences');
// # Click "Desktop Notifications"
cy.get('#desktopTitle').
scrollIntoView().
should('be.visible').
and('contain', 'Send desktop notifications').click();
// # Select mentions category for messages.
cy.get('#channelNotificationMentions').scrollIntoView().check();
if (type === 'ALL') {
// # Check notify for all replies.
cy.get('#desktopThreadsNotificationAllActivity').scrollIntoView().check().should('be.checked');
} else if (type === 'MENTION') {
// # Check notify only for mentions.
cy.get('#desktopThreadsNotificationAllActivity').scrollIntoView().uncheck().should('not.be.checked');
}
// # Click "Save" and close the modal
cy.uiSaveAndClose();
}

Просмотреть файл

@@ -0,0 +1,50 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @collapsed_reply_threads
describe('Collapsed Reply Threads', () => {
let testTeam;
before(() => {
cy.apiUpdateConfig({
ServiceSettings: {
ThreadAutoFollow: true,
CollapsedThreads: 'default_off',
},
});
// # Create new channel and visit channel
cy.apiInitSetup({loginAfter: true}).then(({team, channel}) => {
testTeam = team;
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
});
});
it('MM-T4140 should be able to toggle CRT on/off', () => {
// # Set CRT to off
cy.uiChangeCRTDisplaySetting('OFF');
// * Threads menu in sidebar should not be visible
cy.get('.SidebarGlobalThreads').should('not.exist');
// # Set CRT to on
cy.uiChangeCRTDisplaySetting('ON');
// * Threads menu in sidebar should be visible
cy.get('.SidebarGlobalThreads').should('exist');
// # Visit global threads
cy.visit(`/${testTeam.name}/threads`);
// * should see followed threads in H2 title
cy.get('h2').should('have.text', 'Followed threads');
});
});

Просмотреть файл

@@ -0,0 +1,389 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @collapsed_reply_threads
describe('Collapsed Reply Threads', () => {
let testTeam;
let otherUser;
let testChannel;
let rootPost;
beforeEach(() => {
cy.apiUpdateConfig({
ServiceSettings: {
ThreadAutoFollow: true,
CollapsedThreads: 'default_off',
},
});
// # Log in as user that hasn't had CRT enabled before
cy.apiInitSetup({loginAfter: true, promoteNewUserAsAdmin: true, userPrefix: 'tipbutton'}).then(({team, channel}) => {
testTeam = team;
testChannel = channel;
cy.apiCreateUser({prefix: 'other'}).then(({user: user1}) => {
otherUser = user1;
cy.apiAddUserToTeam(testTeam.id, otherUser.id).then(() => {
cy.apiAddUserToChannel(testChannel.id, otherUser.id);
// # Post a message as other user
cy.postMessageAs({sender: otherUser, message: 'Root post', channelId: testChannel.id}).then((post) => {
rootPost = post;
});
});
});
// # Visit the channel
cy.visit(`/${team.name}/channels/${channel.name}`);
});
});
it('MM-T4684 CRT - CRT tour - full', () => {
// # Go to Settings>Display>Display Settings>Collapsed Reply Threads (Beta), select ON and Save
// # Dismiss "You're accessing an early beta of CRT" modal
cy.uiChangeCRTDisplaySetting('ON');
// # Open any post on RHS
cy.uiGetNthPost(1).click();
// * Verify green dot on Thread title on the thread header
cy.get('.sidebar--right__header').find('#tipButton').should('be.visible');
// * Verify 1st tutorial tip points is present
cy.get('[data-testid="current_tutorial_tip"]').findByText('Viewing a thread in the sidebar').should('be.visible');
// * Verify "Got it" button is present
cy.findByText('Got it').should('be.visible');
// # Click on "Got it" button
cy.findByText('Got it').click();
// * Verify tutorial tip is dismissed
cy.get('[data-testid="current_tutorial_tip"]').should('not.exist');
// * Verify green dot is is no longer present
cy.get('.sidebar--right__header').find('#tipButton').should('not.exist');
// # Follow an existing thread or receive replies to root post you started
cy.get('#rhsContainer').find('.FollowButton').click();
cy.postMessageAs({sender: otherUser, message: 'other reply!', channelId: testChannel.id, rootId: rootPost.id});
// * Verify green pulsing dot on global threads
cy.get('#sidebar-threads-button').find('[data-testid="pulsating_dot"]').should('be.visible');
// # Click on global Threads sidebar item
cy.uiGetSidebarItem('threads').click();
// * Verify "A new way to view and follow thread" modal is present
cy.get('#collapsed_reply_threads_modal').should('be.visible').as('crtModal');
cy.get('@crtModal').findByText('A new way to view and follow threads').should('be.visible');
// * Verify "Take the Tour" button is present
cy.get('@crtModal').findByText('Take the Tour').should('be.visible');
// * Verify "Skip Tour" button is present
cy.get('@crtModal').findByText('Skip Tour').should('be.visible');
// # Click on Take the tour button
cy.get('@crtModal').findByText('Take the Tour').click();
// * Verify "Welcome to the Threads view!" tour tip
cy.get('[data-testid="current_tutorial_tip"]').findByText('Welcome to the Threads view!').should('be.visible');
// * Verify Next button is present
cy.findByText('Next').should('be.visible');
// * Verify 3 radio buttons on the bottom, with the far left button active
cy.get('.tour-tip__dot-ctr').should('be.visible').children().as('crtTourTip');
cy.get('@crtTourTip').eq(0).find('a').should('have.class', 'tour-tip__dot active');
cy.get('@crtTourTip').eq(1).find('a').should('have.class', 'tour-tip__dot');
cy.get('@crtTourTip').eq(2).find('a').should('have.class', 'tour-tip__dot');
// # Click on the Next button
cy.findByText('Next').click();
// * Verify green dot on first thread
cy.get('[data-testid="threads_list"]').find('#tipButton').should('be.visible');
// * Verify "Threads List" tutorial tip
cy.get('[data-testid="current_tutorial_tip"]').should('be.visible').within(() => {
cy.findByText('Threads List').should('be.visible');
});
// * Verify "Previous" button is present
cy.findByText('Previous').should('be.visible');
// * Verify "Next" button is present
cy.findByText('Next').should('be.visible');
// * Verify middle radio button is active
cy.get('.tour-tip__dot-ctr').should('be.visible').children().as('crtTourTip');
cy.get('@crtTourTip').eq(0).find('a').should('have.class', 'tour-tip__dot');
cy.get('@crtTourTip').eq(1).find('a').should('have.class', 'tour-tip__dot active');
cy.get('@crtTourTip').eq(2).find('a').should('have.class', 'tour-tip__dot');
// # Click on Next
cy.findByText('Next').click();
// * Verify green dot on Unreads tab
cy.get('#threads-list-unread-button').find('#tipButton').should('be.visible');
// * Verify "Unread threads" tutorial tip
cy.get('[data-testid="current_tutorial_tip"]').should('be.visible').findByText('Unread threads').should('be.visible');
// * Verify "Previous" button is present
cy.findByText('Previous').should('be.visible');
// * Verify "Done" button is present
cy.findByText('Done').should('be.visible');
// * Verify far right radio button is active
// * Verify 3 radio buttons on the bottom, with the far left button active
cy.get('.tour-tip__dot-ctr').should('be.visible').children().as('crtTourTip');
cy.get('@crtTourTip').eq(0).find('a').should('have.class', 'tour-tip__dot');
cy.get('@crtTourTip').eq(1).find('a').should('have.class', 'tour-tip__dot');
cy.get('@crtTourTip').eq(2).find('a').should('have.class', 'tour-tip__dot active');
// # Click on "Done" button
cy.findByText('Done').click();
// * Verify tour is dismissed
cy.get('[data-testid="current_tutorial_tip"]').should('not.exist');
cy.get('[data-testid="threads_list"]').find('#tipButton').should('not.exist');
cy.get('#threads-list-unread-button').find('#tipButton').should('not.exist');
cy.get('#sidebar-threads-button').find('[data-testid="pulsating_dot"]').should('not.exist');
});
it('MM-T4694 CRT - Skip the tour', () => {
// # Go to Settings>Display>Display Settings>Collapsed Reply Threads (Beta), select ON and Save
// # Dismiss "You're accessing an early beta of CRT" modal
cy.uiChangeCRTDisplaySetting('ON');
// # Open any post on RHS
cy.uiGetNthPost(1).click();
// * Verify green dot on Thread title on the thread header
cy.get('.sidebar--right__header').find('#tipButton').should('be.visible');
// * Verify 1st tutorial tip points is present
cy.get('[data-testid="current_tutorial_tip"]').findByText('Viewing a thread in the sidebar').should('be.visible');
// * Verify "Got it" button is present
cy.findByText('Got it').should('be.visible');
// # Click on "x" button to dismiss the tour point
cy.get('[data-testid="close_tutorial_tip"]').click();
// * Verify tutorial tip is dismissed
cy.get('[data-testid="current_tutorial_tip"]').should('not.exist');
// * Verify green dot is is no longer present
cy.get('.sidebar--right__header').find('#tipButton').should('not.exist');
// # Follow an existing thread or receive replies to root post you started
cy.get('#rhsContainer').find('.FollowButton').click();
cy.postMessageAs({sender: otherUser, message: 'other reply!', channelId: testChannel.id, rootId: rootPost.id});
// * Verify green pulsing dot on global threads
cy.get('#sidebar-threads-button').find('[data-testid="pulsating_dot"]').should('be.visible');
// # Click on global Threads sidebar item
cy.uiGetSidebarItem('threads').click();
// * Verify "A new way to view and follow thread" modal is present
cy.get('#collapsed_reply_threads_modal').should('be.visible').within(() => {
cy.findByText('A new way to view and follow threads').should('be.visible');
// * Verify "Take the Tour" button is present
cy.findByText('Take the Tour').should('be.visible');
// * Verify "Skip Tour" button is present
cy.findByText('Skip Tour').should('be.visible');
// # Click on "x" button to dismiss the tour point
cy.get('.close').click();
});
// * Verify tutorial modal is dismissed
cy.get('[data-testid="current_tutorial_tip"]').should('not.exist');
// * Verify green dot is still present
cy.get('#sidebar-threads-button').find('[data-testid="pulsating_dot"]').should('be.visible');
// # Click on global Threads sidebar item
cy.uiGetSidebarItem('threads').click();
// # Click on "Skip tour" button
cy.get('#collapsed_reply_threads_modal').should('be.visible').findByText('Skip Tour').click();
// * Verify modal is dismissed and tour is no longer available
cy.get('[data-testid="current_tutorial_tip"]').should('not.exist');
cy.get('[data-testid="threads_list"]').find('#tipButton').should('not.exist');
cy.get('#threads-list-unread-button').find('#tipButton').should('not.exist');
// * Verify no pulsing green dot on global threads item
cy.get('#sidebar-threads-button').find('[data-testid="pulsating_dot"]').should('not.exist');
});
it('MM-T4695 CRT - cancel any tour point by using x', () => {
// # Go to Settings>Display>Display Settings>Collapsed Reply Threads (Beta), select ON and Save
// # Dismiss "You're accessing an early beta of CRT" modal
cy.uiChangeCRTDisplaySetting('ON');
// # Follow an existing thread or receive replies to root post you started
cy.postMessageAs({sender: otherUser, message: 'other reply!', channelId: testChannel.id, rootId: rootPost.id});
cy.get(`#post_${rootPost.id}`).find('.FollowButton').click();
// * Verify green pulsing dot on global threads
cy.get('#sidebar-threads-button').find('[data-testid="pulsating_dot"]').should('be.visible');
// # Click on global Threads sidebar item
cy.uiGetSidebarItem('threads').click();
// * Verify "A new way to view and follow thread" modal is present
cy.get('#collapsed_reply_threads_modal').should('be.visible').within(() => {
cy.findByText('A new way to view and follow threads').should('be.visible');
// * Verify "Take the Tour" button is present
cy.findByText('Take the Tour').should('be.visible');
// * Verify "Skip Tour" button is present
cy.findByText('Skip Tour').should('be.visible');
// # Click on Take the tour button
cy.findByText('Take the Tour').click();
});
// * Verify "Welcome to the Threads view!" tutorial tip
cy.get('[data-testid="current_tutorial_tip"]').should('be.visible').within(() => {
cy.findByText('Welcome to the Threads view!').should('be.visible');
});
// * Verify Next button is present
cy.findByText('Next').should('be.visible');
// * Verify 3 radio buttons on the bottom, with the far left button active
cy.get('.tour-tip__dot-ctr').should('be.visible').children().as('crtTourTip');
cy.get('@crtTourTip').eq(0).find('a').should('have.class', 'tour-tip__dot active');
cy.get('@crtTourTip').eq(1).find('a').should('have.class', 'tour-tip__dot');
cy.get('@crtTourTip').eq(2).find('a').should('have.class', 'tour-tip__dot');
// # Click on the `x` to dismiss the tip
cy.get('[data-testid="close_tutorial_tip"]').click();
// * Verify green dot on Threads sidebar item
cy.get('#SidebarContainer').find('#tipButton').should('be.visible').click();
cy.findByText('Next').click();
// # Click on the `x` to dismiss the tip
cy.get('[data-testid="close_tutorial_tip"]').click();
// * Verify green pulsing dot on the first, top, thread
cy.get('[data-testid="threads_list"]').within(() => {
cy.get('#tipButton').should('be.visible');
// # Click on the green dot
cy.get('#tipButton').click();
});
// * Verify green dot on first thread
cy.get('[data-testid="threads_list"]').find('#tipButton').should('be.visible');
// * Verify "Threads List" tutorial tip
cy.get('[data-testid="current_tutorial_tip"]').should('be.visible').within(() => {
cy.findByText('Threads List').should('be.visible');
});
// * Verify "Previous" button is present
cy.findByText('Previous').should('be.visible');
// * Verify "Next" button is present
cy.findByText('Next').should('be.visible');
// * Verify middle radio button is active
cy.get('.tour-tip__dot-ctr').should('be.visible').children().as('crtTourTip');
cy.get('@crtTourTip').eq(0).find('a').should('have.class', 'tour-tip__dot');
cy.get('@crtTourTip').eq(1).find('a').should('have.class', 'tour-tip__dot active');
cy.get('@crtTourTip').eq(2).find('a').should('have.class', 'tour-tip__dot');
// # Click on the Previous button
cy.findByText('Previous').click();
// * Verify previous tutorial tip opens
cy.get('[data-testid="current_tutorial_tip"]').should('be.visible').within(() => {
cy.findByText('Welcome to the Threads view!').should('be.visible');
});
// * Verify far left radio button is active
cy.get('.tour-tip__dot-ctr').should('be.visible').children().as('crtTourTip');
cy.get('@crtTourTip').eq(0).find('a').should('have.class', 'tour-tip__dot active');
cy.get('@crtTourTip').eq(1).find('a').should('have.class', 'tour-tip__dot');
cy.get('@crtTourTip').eq(2).find('a').should('have.class', 'tour-tip__dot');
// # Click Next and ...
cy.findByText('Next').click();
// # ... then x to dismiss the Threads List tip
cy.get('[data-testid="current_tutorial_tip"]').should('be.visible').within(() => {
cy.findByText('Threads List').should('be.visible');
cy.get('[data-testid="close_tutorial_tip"]').click();
});
// * Verify tip is dismissed
cy.get('[data-testid="current_tutorial_tip"]').should('not.exist');
cy.get('[data-testid="threads_list"]').find('#tipButton').should('be.visible').click();
// # Click Next and ...
cy.findByText('Next').click();
cy.get('[data-testid="close_tutorial_tip"]').click();
// * Verify green dot on Unreads tab
cy.get('#threads-list-unread-button').within(() => {
cy.get('#tipButton').should('be.visible');
});
// # Click on the green dot on Unreads tab
cy.get('#tipButton').click();
// * Verify "Unread threads" tutorial tip
cy.get('[data-testid="current_tutorial_tip"]').should('be.visible').within(() => {
cy.findByText('Unread threads').should('be.visible');
});
// * Verify "Previous" button is present
cy.findByText('Previous').should('be.visible');
// * Verify "Finish tour" button is present
cy.findByText('Done').should('be.visible');
// * Verify far right radio button is active
cy.get('.tour-tip__dot-ctr').should('be.visible').children().as('crtTourTip');
cy.get('@crtTourTip').eq(0).find('a').should('have.class', 'tour-tip__dot');
cy.get('@crtTourTip').eq(1).find('a').should('have.class', 'tour-tip__dot');
cy.get('@crtTourTip').eq(2).find('a').should('have.class', 'tour-tip__dot active');
// # Click on `x` to dismiss tutorial tip
cy.get('[data-testid="close_tutorial_tip"]').click();
// * Verify tour is dismissed
cy.get('#threads-list-unread-button').find('#tipButton').should('be.visible').click();
cy.findByText('Done').should('be.visible').click();
cy.get('#threads-list-unread-button').find('#tipButton').should('not.exist');
cy.get('[data-testid="current_tutorial_tip"]').should('not.exist');
cy.get('[data-testid="threads_list"]').find('#tipButton').should('not.exist');
cy.get('#sidebar-threads-button').find('[data-testid="pulsating_dot"]').should('not.exist');
});
});

Просмотреть файл

@@ -0,0 +1,98 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @collapsed_reply_threads @not_cloud
import * as MESSAGES from '../../../fixtures/messages';
import {matterpollPlugin} from '../../../utils/plugins';
import {interceptFileUpload} from '../files_and_attachments/helpers';
describe('Collapsed Reply Threads', () => {
let testTeam;
let testChannel;
let user1;
before(() => {
cy.apiUpdateConfig({
PluginSettings: {
Enable: true,
},
ServiceSettings: {
ThreadAutoFollow: true,
CollapsedThreads: 'default_off',
},
});
// # Create new channel and other user, and add other user to channel
cy.apiInitSetup({loginAfter: true, promoteNewUserAsAdmin: true}).then(({team, channel, user}) => {
testTeam = team;
user1 = user;
testChannel = channel;
cy.apiSaveCRTPreference(user1.id, 'on');
});
});
beforeEach(() => {
// # Visit the channel
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
interceptFileUpload();
});
it('MM-T4776 should display poll text without Markdown in the threads list', () => {
cy.shouldNotRunOnCloudEdition();
cy.shouldHavePluginUploadEnabled();
// # Upload and enable "matterpoll" plugin
cy.apiUploadAndEnablePlugin(matterpollPlugin);
// # In center post the following: /poll "Do you like https://mattermost.com?"
cy.postMessage('/poll "Do you like https://mattermost.com?"');
cy.getLastPostId().then((pollId) => {
// # Post a reply on the POLL to create a thread and follow
cy.postMessageAs({sender: user1, message: MESSAGES.SMALL, channelId: testChannel.id, rootId: pollId});
// # Click "Yes" or "No"
cy.findByText('Yes').click();
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * Text in ThreadItem should say 'username: Do you like https://mattermost.com?'
cy.get('.attachment__truncated').first().should('have.text', user1.nickname + ': Do you like https://mattermost.com?');
// * Text in ThreadItem should say 'Total votes: 1'
cy.get('.attachment__truncated').last().should('have.text', 'Total votes: 1');
// # Open the thread
cy.get('.ThreadItem').last().click();
// # Go to channel
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
// # End the poll
cy.findByText('End Poll').click();
cy.findByText('End').click();
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * Text in ThreadItem should say 'username: Do you like https://mattermost.com?'
cy.get('.attachment__truncated').first().should('have.text', user1.nickname + ': Do you like https://mattermost.com?');
// * Text in ThreadItem should say 'This poll has ended. The results are:'
cy.get('.attachment__truncated').last().should('have.text', 'This poll has ended. The results are:');
// # Cleanup
cy.apiDeletePost(pollId);
});
});
});

Просмотреть файл

@@ -0,0 +1,142 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @collapsed_reply_threads
import * as MESSAGES from '../../../fixtures/messages';
import {waitUntilUploadComplete, interceptFileUpload} from '../files_and_attachments/helpers';
describe('Collapsed Reply Threads', () => {
let testTeam;
let testChannel;
let user1;
const files = [
{
testCase: 'MM-T4777_2',
filename: 'word-file.doc',
extensions: 'DOC',
icon: 'icon-file-word-outline',
},
{
testCase: 'MM-T4777_3',
filename: 'wordx-file.docx',
extensions: 'DOCX',
icon: 'icon-file-word-outline',
},
{
testCase: 'MM-T4777_4',
filename: 'powerpoint-file.ppt',
extensions: 'PPT',
icon: 'icon-file-powerpoint-outline',
},
{
testCase: 'MM-T4777_5',
filename: 'powerpointx-file.pptx',
extensions: 'PPTX',
icon: 'icon-file-powerpoint-outline',
},
{
testCase: 'MM-T4777_6',
filename: 'mp3-audio-file.mp3',
extensions: 'MP3',
icon: 'icon-file-audio-outline',
},
{
testCase: 'MM-T4777_7',
filename: 'mp4-video-file.mp4',
extensions: 'MP4',
icon: 'icon-file-video-outline',
},
{
testCase: 'MM-T4777_8',
filename: 'theme.json',
extensions: 'JSON',
icon: 'icon-file-code-outline',
},
];
before(() => {
cy.apiUpdateConfig({
ServiceSettings: {
ThreadAutoFollow: true,
CollapsedThreads: 'default_off',
},
});
// # Create new channel and other user, and add other user to channel
cy.apiInitSetup({loginAfter: true, promoteNewUserAsAdmin: true}).then(({team, channel, user}) => {
testTeam = team;
user1 = user;
testChannel = channel;
cy.apiSaveCRTPreference(user1.id, 'on');
});
});
beforeEach(() => {
// # Visit the channel
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
interceptFileUpload();
});
it('MM-T4777_1 should show image thumbnail in thread list item', () => {
const image = 'jpg-image-file.jpg';
cy.get('#advancedTextEditorCell').find('#fileUploadInput').attachFile(image);
waitUntilUploadComplete();
cy.get('.post-image__thumbnail').should('be.visible');
cy.uiGetPostTextBox().clear().type('{enter}');
cy.getLastPostId().then((rootId) => {
// # Post a reply to create a thread and follow
cy.postMessageAs({sender: user1, message: MESSAGES.SMALL, channelId: testChannel.id, rootId});
// # Visit Global Threads
cy.uiClickSidebarItem('threads');
// * Text should be the filename
cy.get('.file_card__name').should('have.text', image);
// * Image should be shown
cy.get('.file_card__image.post-image.small').should('be.visible');
// # Cleanup
cy.apiDeletePost(rootId);
});
});
files.forEach((file) => {
it(`${file.testCase} should display correct icon for ${file.extensions} on threads list`, () => {
// # Post a file
cy.get('#advancedTextEditorCell').find('#fileUploadInput').attachFile(file.filename);
waitUntilUploadComplete();
cy.get('.post-image__thumbnail').should('be.visible');
cy.uiGetPostTextBox().clear().type('{enter}');
cy.getLastPostId().then((rootId) => {
// # Post a reply to create a thread and follow
cy.postMessageAs({sender: user1, message: MESSAGES.SMALL, channelId: testChannel.id, rootId});
// # Visit Global Threads
cy.uiClickSidebarItem('threads');
// * Thread item should display the correct icon
cy.get('.file_card__attachment').should('have.class', file.icon);
// * Thread item should display text
cy.get('.file_card__name').should('have.text', file.filename);
// # Cleanup
cy.apiDeletePost(rootId);
});
});
});
});

Просмотреть файл

@@ -0,0 +1,274 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Group: @channels @collapsed_reply_threads
import * as TIMEOUTS from '../../../fixtures/timeouts';
import {isMac} from '../../../utils';
describe('Collapsed Reply Threads', () => {
let testTeam;
let testUser;
let otherUser;
let testChannel;
before(() => {
cy.apiUpdateConfig({
ServiceSettings: {
ThreadAutoFollow: true,
CollapsedThreads: 'default_off',
},
});
// # Create new channel and other user and add other user to channel
cy.apiInitSetup({loginAfter: true, promoteNewUserAsAdmin: true}).then(({team, channel, user}) => {
testTeam = team;
testUser = user;
testChannel = channel;
cy.apiSaveCRTPreference(testUser.id, 'on');
cy.apiCreateUser({prefix: 'other'}).then(({user: user1}) => {
otherUser = user1;
cy.apiAddUserToTeam(testTeam.id, otherUser.id).then(() => {
cy.apiAddUserToChannel(testChannel.id, otherUser.id);
});
});
});
});
beforeEach(() => {
// # Visit channel
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
});
it('MM-T4141_1 should follow a thread after replying', () => {
// # Post message as other user
cy.postMessageAs({
sender: otherUser,
message: 'Root post,',
channelId: testChannel.id,
}).then(({id: rootId}) => {
// * Thread footer should not be visible
cy.get(`#post_${rootId}`).find('.ThreadFooter').should('not.exist');
// # Click on post to open RHS
cy.get(`#post_${rootId}`).click();
// * Button on header should say Follow as current user is not following
cy.get('#rhsContainer').find('.FollowButton').should('have.text', 'Follow');
// # Post a reply as current user
cy.postMessageReplyInRHS('Reply!');
// # Get last root post
cy.get(`#post_${rootId}`).
// * thread footer should exist now
get('.ThreadFooter').should('exist').
within(() => {
// * the button on the footer should say Following
cy.get('.FollowButton').should('have.text', 'Following');
});
// * the button on the RHS header should now say Following
cy.get('#rhsContainer').find('.FollowButton').should('have.text', 'Following');
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * There should be a thread there
cy.get('article.ThreadItem').should('have.have.lengthOf', 1);
});
});
it('MM-T4141_2 should follow a thread after marking it as unread', () => {
// # Post a root post as other user
postMessageWithReply(testChannel.id, otherUser, 'Another interesting post,', otherUser, 'Self reply!').then(({rootId, replyId}) => {
// # Get root post
cy.get(`#post_${rootId}`).within(() => {
// * Thread footer should be visible
cy.get('.ThreadFooter').should('exist').
// * Thread footer button should say 'Follow'
find('.FollowButton').should('have.text', 'Follow');
});
// # Click on root post to open thread
cy.get(`#post_${rootId}`).click();
// * RHS header button should say 'Follow'
cy.get('#rhsContainer').find('.FollowButton').should('have.text', 'Follow');
// # Click on the reply's dot menu and mark as unread
cy.uiClickPostDropdownMenu(replyId, 'Mark as Unread', 'RHS_COMMENT');
// * RHS header button should say 'Following'
cy.get('#rhsContainer').find('.FollowButton').should('have.text', 'Following');
// # Get root post
cy.get(`#post_${rootId}`).within(() => {
// * Thread footer should be visible
cy.get('.ThreadFooter').should('exist').
// * Thread footer button should say 'Following'
find('.FollowButton').should('have.text', 'Following');
});
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * There should be 2 threads now
cy.get('article.ThreadItem').should('have.have.lengthOf', 2);
});
});
it('MM-T4141_3 clicking "Following" button in the footer should unfollow the thread', () => {
// # Post a root post as other user
postMessageWithReply(testChannel.id, otherUser, 'Another interesting post,', testUser, 'Self reply!');
// # Get last root post in channel
cy.getLastPostId().then((rootId) => {
// # Open the thread
cy.get(`#post_${rootId}`).click();
// * RHS header button should say 'Following'
cy.get('#rhsContainer').find('.FollowButton').should('have.text', 'Following');
// # Get thread footer of last root post
cy.get(`#post_${rootId}`).within(() => {
// * thread footer should exist
cy.get('.ThreadFooter').should('exist');
// * thread footer button should say 'Following'
cy.get('.FollowButton').should('have.text', 'Following');
// # Click thread footer Following button
cy.get('.FollowButton').click({force: true});
// * thread footer button should say 'Follow'
cy.get('.FollowButton').should('have.text', 'Follow');
});
// * RHS header button should say 'Follow'
cy.get('#rhsContainer').find('.FollowButton').should('have.text', 'Follow');
// # Close RHS
cy.uiCloseRHS();
});
});
it('MM-T4141_4 clicking "Follow" button in the footer should follow the thread', () => {
// # Post a root post as other user
postMessageWithReply(testChannel.id, otherUser, 'Another interesting post,', otherUser, 'Self reply!');
// # Get last root post in channel
cy.getLastPostId().then((rootId) => {
// # Open the thread
cy.get(`#post_${rootId}`).click();
// * RHS header button should say 'Follow'
cy.get('#rhsContainer').find('.FollowButton').should('have.text', 'Follow');
// # Get thread footer of last root post
cy.get(`#post_${rootId}`).within(() => {
// * thread footer should exist
cy.get('.ThreadFooter').should('exist');
// * thread footer button should say 'Follow'
cy.get('.FollowButton').should('have.text', 'Follow');
// # Click thread footer 'Follow' button
cy.get('.FollowButton').click({force: true});
// * thread footer button should say 'Following'
cy.get('.FollowButton').should('have.text', 'Following');
});
// * RHS header button should say 'Following'
cy.get('#rhsContainer').find('.FollowButton').should('have.text', 'Following');
// # Close RHS
cy.uiCloseRHS();
});
});
it('MM-T4682 should show search guidance at the end of the list after scroll loading', () => {
// # Create more than 25 threads so we can use scroll loading in the Threads list
for (let i = 1; i <= 30; i++) {
postMessageWithReply(testChannel.id, otherUser, `Another interesting post ${i}`, testUser, `Another reply ${i}!`).then(({rootId}) => {
// # Mark last thread as Unread
if (i === 30) {
// # Click on root post to open thread
cy.get(`#post_${rootId}`).click();
// # Click on the reply's dot menu and mark as unread
cy.uiClickPostDropdownMenu(rootId, 'Mark as Unread', 'RHS_ROOT');
}
});
}
cy.uiClickSidebarItem('threads');
// # Scroll load the threads list to reach the end
const maxScrolls = 3;
scrollThreadsListToEnd(maxScrolls);
// * Search guidance item should be shown at the end of the threads list
cy.get('.ThreadList .no-results__wrapper').should('be.visible').within(() => {
// * Title, subtitle and shortcut keys should be shown
cy.findByText('Thats the end of the list').should('be.visible');
cy.contains('If youre looking for older conversations, try searching with ').should('be.visible').within(() => {
cy.findByText(isMac() ? '⌘' : 'Ctrl').should('be.visible');
cy.findByText('Shift').should('be.visible');
cy.findByText('F').should('be.visible');
});
});
// # Click Unreads button
cy.findByText('Unreads').click();
// * Search guidance item should not be shown at the end of the Unreads threads list
cy.get('.ThreadList .no-results__wrapper').should('not.exist');
});
});
function postMessageWithReply(channelId, postSender, postMessage, replySender, replyMessage) {
return cy.postMessageAs({
sender: postSender,
message: postMessage || 'Another interesting post.',
channelId,
}).then(({id: rootId}) => {
cy.postMessageAs({
sender: replySender || postSender,
message: replyMessage || 'Another reply!',
channelId,
rootId,
}).then(({id: replyId}) => (Promise.resolve({rootId, replyId})));
});
}
function scrollThreadsListToEnd(maxScrolls = 1, scrolls = 0) {
if (scrolls === maxScrolls) {
return;
}
cy.get('.ThreadList .virtualized-thread-list').scrollTo('bottom').then(($el) => {
const element = $el.find('.no-results__wrapper');
if (element.length < 1) {
cy.wait(TIMEOUTS.ONE_SEC).then(() => {
scrollThreadsListToEnd(maxScrolls, scrolls + 1);
});
} else {
cy.wrap(element).scrollIntoView();
}
});
}

Просмотреть файл

@@ -0,0 +1,415 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Group: @channels @collapsed_reply_threads
describe('Collapsed Reply Threads', () => {
let testTeam;
let testChannel;
let user1;
let user2;
let user3;
let rootPost;
let replyPost1;
let replyPost2;
const messages = {
ROOT: 'ROOT POST',
REPLY1: 'REPLY 1',
REPLY2: 'REPLY 2',
};
before(() => {
cy.apiUpdateConfig({
ServiceSettings: {
ThreadAutoFollow: true,
CollapsedThreads: 'default_off',
},
});
// # Create new channel and other user, and add other user to channel
cy.apiInitSetup({loginAfter: true, promoteNewUserAsAdmin: true}).then(({team, channel, user}) => {
testTeam = team;
user1 = user;
testChannel = channel;
cy.apiSaveCRTPreference(user1.id, 'on');
cy.apiCreateUser({prefix: 'user2'}).then(({user: newUser}) => {
user2 = newUser;
cy.apiAddUserToTeam(testTeam.id, user2.id).then(() => {
cy.apiAddUserToChannel(testChannel.id, user2.id);
});
});
cy.apiCreateUser({prefix: 'user3'}).then(({user: newUser}) => {
user3 = newUser;
cy.apiAddUserToTeam(testTeam.id, user3.id).then(() => {
cy.apiAddUserToChannel(testChannel.id, user3.id);
});
});
});
});
beforeEach(() => {
// # Visit the channel
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
// # Post a message as other user
cy.postMessageAs({sender: user1, message: messages.ROOT, channelId: testChannel.id}).then((post) => {
rootPost = post;
});
});
it('MM-T4379 Display: Click to open threads)', () => {
cy.uiWaitUntilMessagePostedIncludes(rootPost.data.message);
// # Get the root post
cy.getLastPost().click();
// * Verify the post is opened in RHS
cy.get(`#rhsPost_${rootPost.id}`).should('be.visible');
// # Close RHS
cy.uiCloseRHS();
// # Open settings modal and disable opening the thread in RHS when being clicked
cy.uiOpenSettingsModal('Display');
// # Open section "click to open threads"
cy.get('#click_to_replyTitle').click();
// # Click radio button with option B ('off')
cy.get('#click_to_replyFormatB').click();
// # Save settings
cy.get('#saveSetting').click();
// # close settings modal
cy.uiClose();
// # (Re-) Visit the channel
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
// # Get the root post
cy.getLastPost().click();
// * Verify the post is opened in RHS
cy.get(`rhsPost_${rootPost.id}`).should('not.exist');
// # Cleanup for next test
cy.apiDeletePost(rootPost.id);
});
it('MM-T4445 CRT - Delete root post', () => {
/**
* When you delete a post the current behavior in displaying it and the thread replies is incorrect.
* Deleting a root post leads to a post showing `(message deleted)`, but still rendering the replies
* in the ThreadFooter. Ideally we should remove the root post without removing the replies.
*/
cy.uiWaitUntilMessagePostedIncludes(rootPost.data.message);
// # Thread footer should not exist
cy.uiGetPostThreadFooter(rootPost.id).should('not.exist');
// # Post a reply post as current user
cy.postMessageAs({sender: user2, message: messages.REPLY1, channelId: testChannel.id, rootId: rootPost.id});
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * There should be a single thread item
cy.get('article.ThreadItem').should('have.lengthOf', 1);
// # Delete thread root post
cy.apiDeletePost(rootPost.id);
/**
* TODO: this should not be there once the root post is deleted, so remove it once the feature is adjusted
*/
// * There should be a single thread item showing '(message deleted)'
cy.get('article.ThreadItem').should('have.lengthOf', 1).should('contain.text', '(message deleted)');
// # Refresh the page
cy.reload(true);
// * There should be no thread item anymore
cy.get('article.ThreadItem').should('have.lengthOf', 0);
});
it('MM-T4446 CRT - Delete single reply post on a thread', () => {
/**
* When you delete a reply you are still following the thread, so the thread is not being removed
* from the global threads view. The test description was written differently as it was assuming the
* thread to not be followed after that. The test will be build keeping that in mind, but should
* thread to not be followed after that. The test will be build keeping that in mind, but should
* probably be adjusted later on.
*/
cy.uiWaitUntilMessagePostedIncludes(rootPost.data.message);
// # Thread footer should not exist
cy.uiGetPostThreadFooter(rootPost.id).should('not.exist');
// # Post a reply post as current user
cy.postMessageAs({sender: user1, message: messages.REPLY1, channelId: testChannel.id, rootId: rootPost.id}).then((post) => {
replyPost1 = post;
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * There should be a single thread item
cy.get('article.ThreadItem').should('have.lengthOf', 1).first().click();
// * Reply should be in RHS
cy.get(`#rhsPostMessageText_${replyPost1.id}`).should('be.visible').should('contain.text', messages.REPLY1);
// # Delete thread reply post
cy.apiDeletePost(replyPost1.id);
// * There should be a single thread item
cy.get('article.ThreadItem').should('have.lengthOf', 1);
// * The reply should be in RHS showing '(message deleted)'
cy.get(`#rhsPost_${replyPost1.id}`).should('be.visible').should('contain.text', '(message deleted)');
// # Reload to re-render UI
cy.reload(true);
// * There should be a single thread item with no reply
cy.get('article.ThreadItem').should('have.lengthOf', 1);
// * The reply post should not exist anymore
cy.get(`#rhsPost_${replyPost1.id}`).should('not.exist');
// # Cleanup for next test
cy.apiDeletePost(rootPost.id);
});
});
it('MM-T4447 CRT - Delete single reply post on a multi-reply thread', () => {
cy.uiWaitUntilMessagePostedIncludes(rootPost.data.message);
// # Thread footer should not exist
cy.uiGetPostThreadFooter(rootPost.id).should('not.exist');
// # Post a reply post as current user
cy.postMessageAs({sender: user2, message: messages.REPLY1, channelId: testChannel.id, rootId: rootPost.id}).then((post) => {
replyPost1 = post;
}).then(() => {
return cy.postMessageAs({sender: user3, message: messages.REPLY2, channelId: testChannel.id, rootId: rootPost.id});
}).then((post) => {
replyPost2 = post;
// # Get thread footer of last post
cy.uiGetPostThreadFooter(rootPost.id).within(() => {
// * Reply button in Thread Footer should say '2 replies'
cy.get('.ReplyButton').should('have.text', '2 replies');
// * 2 avatars/participants should show in Thread Footer
cy.get('.Avatar').should('have.lengthOf', 2);
});
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * There should be a single thread item
cy.get('article.ThreadItem').should('have.lengthOf', 1).first().click().within(() => {
// * Activity section in ThreadItem should say '2 replies'
cy.get('.activity').should('have.text', '2 replies');
// * 3 avatars/participants should show in ThreadItem
cy.get('.Avatar').should('have.lengthOf', 3);
});
// * The reply should be in RHS showing '(message deleted)'
cy.get(`#rhsPost_${replyPost2.id}`).should('be.visible').should('contain.text', messages.REPLY2);
// # Delete thread reply post
cy.apiDeletePost(replyPost2.id);
// # Reload to re-render UI
cy.reload(true);
// * There should be a single thread item
cy.get('article.ThreadItem').should('have.lengthOf', 1).first().click().within(() => {
// * Activity section in ThreadItem should say '1 reply'
cy.get('.activity').should('have.text', '1 reply');
// * 2 avatars/participants should show in ThreadItem
cy.get('.Avatar').should('have.lengthOf', 2);
});
// # Visit the channel
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
// # Get thread footer of last post
cy.uiGetPostThreadFooter(rootPost.id).within(() => {
// * Reply button in Thread Footer should say '1 reply'
cy.get('.ReplyButton').should('have.text', '1 reply');
// * 1 avatar/participant should show in Thread Footer
cy.get('.Avatar').should('have.lengthOf', 1);
});
// # Cleanup for next test
cy.apiDeletePost(rootPost.id);
});
});
it('MM-T4448_1 CRT - L16 - Use “Mark all as read” button', () => {
cy.uiWaitUntilMessagePostedIncludes(rootPost.data.message);
// # Thread footer should not exist
cy.uiGetPostThreadFooter(rootPost.id).should('not.exist');
// # Post a reply post as current user
cy.postMessageAs({sender: user2, message: messages.REPLY1, channelId: testChannel.id, rootId: rootPost.id}).then((post) => {
replyPost1 = post;
// # Get thread footer of last post
cy.uiGetPostThreadFooter(rootPost.id).within(() => {
// * Reply button in Thread Footer should say '1 reply'
cy.get('.ReplyButton').should('have.text', '1 reply');
// * 1 avatar/participant should show in Thread Footer
cy.get('.Avatar').should('have.lengthOf', 1);
});
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * The unreads tab button has a blue dot (unread indicator)
cy.get('#threads-list-unread-button .dot').should('have.lengthOf', 1);
// * There should be a single thread item
cy.get('article.ThreadItem').should('have.lengthOf', 1).within(() => {
// * The unread indicator (blue dot) should be present
cy.get('.dot-unreads').should('have.lengthOf', 1);
// * Activity section in ThreadItem should say '1 new reply'
cy.get('.activity').should('have.text', '1 new reply');
});
// # Get the "mark all as read" button (the selector is pretty ambiguous here, but should work for now)
cy.get('#threads-list__mark-all-as-read').click();
// * mark_all_threads_as_read_modal is open
cy.get('#mark-all-threads-as-read-modal').should('exist');
// # Click confirm button
cy.get('button.confirm').contains('Mark all as read').click();
// * mark_all_threads_as_read_modal is closed
cy.get('#mark-all-threads-as-read-modal').should('not.exist');
// * The unreads tab button does NOT have a blue dot (unread indicator)
cy.get('#threads-list-unread-button .dot').should('not.exist');
// * There should be a single thread item
cy.get('article.ThreadItem').should('have.lengthOf', 1).within(() => {
// * The unread indicator (blue dot) should NOT be present
cy.get('.dot-unreads').should('have.lengthOf', 0);
// * Activity section in ThreadItem should say '1 reply'
cy.get('.activity').should('have.text', '1 reply');
});
// * Assert that the RHS shows the correct view
cy.get('.no-results__holder').should('contain.text', 'Looks like youre all caught up');
// # Cleanup for next test
cy.apiDeletePost(rootPost.id);
});
});
it('MM-T4448_2 CRT - L16 - Cancel “Mark all as unread” modal', () => {
cy.uiWaitUntilMessagePostedIncludes(rootPost.data.message);
// # Thread footer should not exist
cy.uiGetPostThreadFooter(rootPost.id).should('not.exist');
// # Post a reply post as current user
cy.postMessageAs({sender: user2, message: messages.REPLY1, channelId: testChannel.id, rootId: rootPost.id}).then((post) => {
replyPost1 = post;
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * The unreads tab button has a blue dot (unread indicator)
cy.get('#threads-list-unread-button .dot').should('have.lengthOf', 1);
// * There should be a single thread item
cy.get('article.ThreadItem').should('have.lengthOf', 1).within(() => {
// * The unread indicator (blue dot) should be present
cy.get('.dot-unreads').should('have.lengthOf', 1);
// * Activity section in ThreadItem should say '1 new reply'
cy.get('.activity').should('have.text', '1 new reply');
});
// # Get the "mark all as read" button
cy.get('#threads-list__mark-all-as-read').click();
// * Verify mark_all_threads_as_read_modal is open
cy.get('#mark-all-threads-as-read-modal').should('exist');
// # Click cancel button
cy.get('button.cancel').contains('Cancel').click();
// * Verify mark_all_threads_as_read_modal is closed
cy.get('#mark-all-threads-as-read-modal').should('not.exist');
// * Verify the unreads tab button still has a blue dot
cy.get('#threads-list-unread-button .dot').should('exist');
// # Click on “Mark all as unread” button again
cy.get('#threads-list__mark-all-as-read').click();
// * Verify mark_all_threads_as_read_modal is open
cy.get('#mark-all-threads-as-read-modal').should('exist');
// # Click close button
cy.get('button.close').click();
// * Verify mark_all_threads_as_read_modal is closed
cy.get('#mark-all-threads-as-read-modal').should('not.exist');
// * Verify the unreads tab button still has a blue dot
cy.get('#threads-list-unread-button .dot').should('exist');
// # Click on “Mark all as unread” button again
cy.get('#threads-list__mark-all-as-read').click();
// # Press ESC key to close the modal
cy.get('body').type('{esc}');
// * Verify mark_all_threads_as_read_modal is closed
cy.get('#mark-all-threads-as-read-modal').should('not.exist');
// * Verify the unreads tab button still has a blue dot
cy.get('#threads-list-unread-button .dot').should('exist');
// * There should be a single thread item
cy.get('article.ThreadItem').should('have.lengthOf', 1).within(() => {
// * The unread indicator (blue dot) should be present
cy.get('.dot-unreads').should('have.lengthOf', 1);
// * Activity section in ThreadItem should say '1 new reply'
cy.get('.activity').should('have.text', '1 new reply');
});
});
// * Assert that the RHS shows the correct view
cy.get('.no-results__holder').should('not.contain.text', 'Looks like youre all caught up');
// # Cleanup for next test
cy.apiDeletePost(rootPost.id);
});
});

Просмотреть файл

@@ -0,0 +1,109 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import * as TIMEOUTS from '../../../fixtures/timeouts';
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @collapsed_reply_threads
describe('Collapsed Reply Threads', () => {
let userA; // Member of team A and B
let teamA;
let teamB;
let offTopicUrlA;
let testChannel;
before(() => {
cy.apiUpdateConfig({
ServiceSettings: {
ThreadAutoFollow: true,
CollapsedThreads: 'default_on',
},
});
cy.apiInitSetup().then(({team, user, offTopicUrl: url}) => {
userA = user;
teamA = team;
offTopicUrlA = url;
cy.apiCreateUser().then(() => {
return cy.apiCreateTeam('team', 'Team');
}).then(({team: otherTeam}) => {
teamB = otherTeam;
return cy.apiAddUserToTeam(teamB.id, userA.id);
}).then(() => {
return cy.apiCreateChannel(teamA.id, 'test', 'Test');
}).then(({channel}) => {
testChannel = channel;
return cy.apiAddUserToChannel(testChannel.id, userA.id);
});
});
});
beforeEach(() => {
// # Log in to Team A with an account that has joined multiple teams.
cy.apiLogin(userA);
// # On an account on two teams, view Team A
cy.visit(offTopicUrlA);
});
it('MM-T4887 should stay on threads view when switching teams', () => {
// # Navigate to the new teams town square
cy.visit(`/${teamA.name}/channels/town-square`);
// # Switch to Team B
cy.get(`#${teamB.name}TeamButton`, {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').click();
// * Verify team display name changes correctly.
cy.uiGetLHSHeader().findByText(teamB.display_name);
// # Go to the Threads view on Team B
cy.uiGetSidebarThreadsButton().click();
cy.wait(TIMEOUTS.ONE_SEC);
// # Switch back to Team A
cy.get(`#${teamA.name}TeamButton`, {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').click();
// Verify url is set up for threads view
cy.url().should('include', `${teamA.name}/threads`);
});
it('MM-T4843_1 should go to threads view when switching a team if that was the last view on that team', () => {
// # Go to the Threads view on Team A
cy.uiGetSidebarThreadsButton().click();
// # Switch to Team B
cy.get(`#${teamB.name}TeamButton`, {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').click();
// * Verify team display name changes correctly.
cy.uiGetLHSHeader().findByText(teamB.display_name);
// # Switch back to Team A
cy.get(`#${teamA.name}TeamButton`, {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').click();
// Verify url is set up for threads view
cy.url().should('include', `${teamA.name}/threads`);
});
it('MM-T4843_2 should go to threads view when threads view is the penultimate view and leave the current channel', () => {
// # Go to the Threads view on Team A
cy.uiGetSidebarThreadsButton().click();
// # Switch to Test Channel
cy.uiClickSidebarItem(testChannel.name);
// # Leave the current channel
cy.uiLeaveChannel();
// Verify url is set up for threads view when thread view is the penultimate view
cy.url().should('include', `${teamA.name}/threads`);
});
});

Просмотреть файл

@@ -0,0 +1,177 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @collapsed_reply_threads
describe('Collapsed Reply Threads', () => {
let testTeam;
let testUser;
let otherUser;
let testChannel;
let rootPost;
let postForAvatar;
before(() => {
cy.apiUpdateConfig({
ServiceSettings: {
ThreadAutoFollow: true,
CollapsedThreads: 'default_off',
},
});
// # Create new channel and other user, and add other user to channel
cy.apiInitSetup({loginAfter: true, promoteNewUserAsAdmin: true}).then(({team, channel, user}) => {
testTeam = team;
testUser = user;
testChannel = channel;
cy.apiSaveCRTPreference(testUser.id, 'on');
cy.apiCreateUser({prefix: 'other'}).then(({user: user1}) => {
otherUser = user1;
cy.apiAddUserToTeam(testTeam.id, otherUser.id).then(() => {
cy.apiAddUserToChannel(testChannel.id, otherUser.id);
// # Post a message as other user
cy.postMessageAs({sender: otherUser, message: 'Root post', channelId: testChannel.id}).then((post) => {
rootPost = post;
});
// # Post a message as other user for clicking avatar test
cy.postMessageAs({sender: otherUser, message: 'Root post for clicking avatar', channelId: testChannel.id}).then((post) => {
postForAvatar = post;
});
});
});
});
});
beforeEach(() => {
// # Visit the channel
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
});
it('MM-T4142 should show number of replies in thread', () => {
cy.uiWaitUntilMessagePostedIncludes(rootPost.data.message);
// # Thread footer should not exist
cy.uiGetPostThreadFooter(rootPost.id).should('not.exist');
// # Post a reply post as current user
cy.postMessageAs({sender: testUser, message: 'reply!', channelId: testChannel.id, rootId: rootPost.id});
// # Get thread footer of last post
cy.uiGetPostThreadFooter(rootPost.id).within(() => {
// * Reply button in Thread Footer should say '1 reply'
cy.get('.ReplyButton').should('have.text', '1 reply');
// * 1 avatar/participant should show in Thread Footer
cy.get('.Avatar').should('have.lengthOf', 1);
});
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * The sole thread item should have text in footer saying '1 reply'
cy.get('article.ThreadItem').find('.activity').should('have.text', '1 reply');
// # Visit the channel
cy.uiClickSidebarItem(testChannel.name);
// # Post another reply as current user
cy.postMessageAs({sender: testUser, message: 'another reply!', channelId: testChannel.id, rootId: rootPost.id});
// # Get thread footer of last post
cy.uiGetPostThreadFooter(rootPost.id).within(() => {
// * Reply button in thread footer should say '2 replies'
cy.get('.ReplyButton').should('have.text', '2 replies');
// * 1 avatar/participant should show in Thread Footer
cy.get('.Avatar').should('have.lengthOf', 1);
});
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * The sole thread item should have text in footer saying '2 replies'
cy.get('article.ThreadItem').find('.activity').should('have.text', '2 replies');
// # Visit the channel
cy.uiClickSidebarItem(testChannel.name);
// # Post another reply as other user
cy.postMessageAs({sender: otherUser, message: 'other reply!', channelId: testChannel.id, rootId: rootPost.id});
// # Get thread footer of last post
cy.uiGetPostThreadFooter(rootPost.id).within(() => {
// * Reply button in thread footer should say '3 replies'
cy.get('.ReplyButton').should('have.text', '3 replies');
// * 2 avatars/participants should show in Thread Footer
cy.get('.Avatar').should('have.lengthOf', 2);
});
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * The sole thread item should have text in footer saying '1 new reply'
cy.get('article.ThreadItem').find('.activity').should('have.text', '1 new reply');
});
it('MM-T4646 should open popover when avatar is clicked', () => {
cy.uiWaitUntilMessagePostedIncludes(postForAvatar.data.message);
// # Post a reply post as current user
cy.postMessageAs({sender: testUser, message: 'reply!', channelId: testChannel.id, rootId: postForAvatar.id});
// # Post another reply as other user
cy.postMessageAs({sender: otherUser, message: 'another reply!', channelId: testChannel.id, rootId: postForAvatar.id});
// # Get thread footer of last post and find avatars
cy.uiGetPostThreadFooter(postForAvatar.id).find('.Avatars').find('button').first().click();
// * Profile popover should be visible and close on ESC
cy.get('div.user-profile-popover').first().should('be.visible').find('#messageButton').type('{esc}');
// # Visit global threads
cy.uiClickSidebarItem('threads');
// * Find the first avatar and click it
cy.get('article.ThreadItem').find('.activity').find('.Avatars').find('button').first().click();
// * Profile popover should be visible and close on ESC
cy.get('div.user-profile-popover').first().should('be.visible').find('#messageButton').type('{esc}');
});
it('MM-T4143 Emoji reaction - type +:+1:', () => {
// # Create a root post
cy.postMessage('Hello!');
cy.getLastPostId().then((postId) => {
// # Click on post to open the thread in RHS
cy.get(`#post_${postId}`).click();
// # Type "+:+1:" in comment box to react to the post with a thumbs-up and post
cy.postMessageReplyInRHS('+:+1:');
// * Thumbs-up reaction displays as reaction on post
cy.get(`#${postId}_message`).within(() => {
cy.findByLabelText('reactions').should('be.visible');
cy.findByLabelText('remove reaction +1').should('be.visible');
});
// * Reacting to a root post should not create a thread (thread footer should not exist)
cy.uiGetPostThreadFooter(postId).should('not.exist');
// # Close RHS
cy.uiCloseRHS();
});
});
});

Просмотреть файл

@@ -0,0 +1,96 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @collapsed_reply_threads
import * as TIMEOUTS from '../../../fixtures/timeouts';
describe('Collapsed Reply Threads', () => {
let testTeam;
let testUser;
let otherUser;
let testChannel;
before(() => {
cy.apiUpdateConfig({
ServiceSettings: {
ThreadAutoFollow: true,
CollapsedThreads: 'default_off',
},
});
// # Create new channel and other user, and add other user to channel
cy.apiInitSetup({loginAfter: true, promoteNewUserAsAdmin: true}).then(({team, channel, user}) => {
testTeam = team;
testUser = user;
testChannel = channel;
cy.apiSaveCRTPreference(testUser.id, 'on');
cy.apiCreateUser({prefix: 'other'}).then(({user: user1}) => {
otherUser = user1;
cy.apiAddUserToTeam(testTeam.id, otherUser.id).then(() => {
cy.apiAddUserToChannel(testChannel.id, otherUser.id);
});
});
});
});
beforeEach(() => {
// # Visit channel
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
});
it('MM-T4144_1 should show a new messages line for an unread thread', () => {
// # Post a root post as current user
cy.postMessageAs({
sender: testUser,
message: 'Another interesting post,',
channelId: testChannel.id,
}).then(({id: rootId}) => {
// # Post multiple replies as other user so that the new messages line is pushed up
Cypress._.times(20, (i) => {
cy.postMessageAs({
sender: otherUser,
message: 'Reply ' + i,
channelId: testChannel.id,
rootId,
});
});
// # Click root post
cy.get(`#post_${rootId}`).click();
// # Wait for RHS to open and scroll to position
cy.wait(TIMEOUTS.ONE_SEC);
// * RHS should open and new messages line should be visible
cy.get('#rhsContainer').findByTestId('NotificationSeparator').should('be.visible');
// # Close RHS
cy.uiCloseRHS();
});
});
it('MM-T4144_2 should not show a new messages line after viewing the thread', () => {
// # Get last message in channel
cy.getLastPostId().then((rootId) => {
// # Click on message
cy.get(`#post_${rootId}`).click();
// * RHS should open and new messages line should NOT be visible
cy.get('#rhsContainer').findByTestId('NotificationSeparator').should('not.exist');
// # Close RHS
cy.uiCloseRHS();
});
});
});