* MM-61173 - channel settings modal: base modal, initial commit, file creation and base component * new enhancements to the base modal creation * revert changes on textbox_links and edit channel header * fix types and add back unintentioned deleted value * add the preview textbox component * extract logic for info tab into its own component * add the purpose input to the window * move other component logic to its own component and code clean up * ability to update channel type * more advances on the archive channel tab * fix unit test in textbox * fix translations * do not show the archive modal in default channel * fix issue with url editor not being resetted on undo action * adjust text and styling for the header and purpose inputs * remove textboxlinks and use button eye icon * adjust test and preview button style * add unit test to channel patch * move logic from parent modal to info tab component * fix border issues and focus back to preview textareas * prevent saving changes when pressing enter when selecting an icon * enhance input component to cover limits validations and enhances tests * set default error message for save changes panel * add props to provide custom value to the buttons * remove channel input errors on reset button click * create new component settings textbox * rename component to advanced textbox and add unit tests * styling of the info tab and add error state to advanced textbox * add logic to prevent tab switch with unsaved changes * adjust url error logic and code clean up * code clean up and enhance comments * add char min length to advanced textbox logic * add the channel settings modal to the new menu * add new test files and fix reset error * remove unused error variables * adjust translations and remove unncesary import * enhance permissions for archive channels and manage channel settings * Adjust permission tree so channel admins can convert from private to public * enhance the test suit around channel conversion type * fix some e2e tests and solve channel input name issue * fix unit test by interacting first with the input element * adjust e2e tests to channel settings modal changes * remove commented tests and implement pr feedback * adjust more pr feedback to the code * more pr feedback enhancements * further enhancements to tab navigation, and adjust more e2e tests * remove unused components and fix e2e tests * revert unnecessary permissions changes * Add name label to textboxes * adjust e2e and unit tests * revert min lenght change value and adjust tests and snapshots * Channel banner settings (#30721) * Added channel banner setting header * Updated section styling * handled animation * handled min and max lengths * cleanup * color change fix * general improvements * Fixed API test * removed unused param className * added e2e tests * test: add channel settings configuration tab test file * Based on the context, here's a concise commit message for this change: feat: Add comprehensive tests for ChannelSettingsConfigurationTab * added some more tests * CI * reverted package-lock.json changes in Playwright * remove extra border from advaced textbox * adjust styling for name label in advance texbox and restart preview state on modal close * sync package.lock in playwright --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com> Co-authored-by: Harshil Sharma <18575143+harshilsharma63@users.noreply.github.com>
849 строки
31 KiB
TypeScript
849 строки
31 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import localforage from 'localforage';
|
|
|
|
import * as TIMEOUTS from '../fixtures/timeouts';
|
|
import {isMac} from '../utils';
|
|
|
|
import {ChainableT} from '../types';
|
|
|
|
// ***********************************************************
|
|
// Read more: https://on.cypress.io/custom-commands
|
|
// ***********************************************************
|
|
|
|
function logout(): ChainableT<any> {
|
|
return cy.get('#logout').click({force: true});
|
|
}
|
|
Cypress.Commands.add('logout', logout);
|
|
|
|
function getCurrentUserId(): ChainableT<Promise<unknown>> {
|
|
return cy.wrap(new Promise((resolve) => {
|
|
cy.getCookie('MMUSERID').then((cookie) => {
|
|
resolve(cookie.value);
|
|
});
|
|
}));
|
|
}
|
|
Cypress.Commands.add('getCurrentUserId', getCurrentUserId);
|
|
|
|
// ***********************************************************
|
|
// Key Press
|
|
// ***********************************************************
|
|
|
|
// Type Cmd or Ctrl depending on OS
|
|
function typeCmdOrCtrl(): ChainableT<any> {
|
|
return typeCmdOrCtrlInt('#post_textbox');
|
|
}
|
|
Cypress.Commands.add('typeCmdOrCtrl', typeCmdOrCtrl);
|
|
|
|
function typeCmdOrCtrlForEdit(): ChainableT<any> {
|
|
return typeCmdOrCtrlInt('#edit_textbox');
|
|
}
|
|
Cypress.Commands.add('typeCmdOrCtrlForEdit', typeCmdOrCtrlForEdit);
|
|
|
|
function typeCmdOrCtrlInt(textboxSelector: string) {
|
|
let cmdOrCtrl: string;
|
|
if (isMac()) {
|
|
cmdOrCtrl = '{cmd}';
|
|
} else {
|
|
cmdOrCtrl = '{ctrl}';
|
|
}
|
|
|
|
return cy.get(textboxSelector).type(cmdOrCtrl, {release: false});
|
|
}
|
|
|
|
function cmdOrCtrlShortcut(subject: string, text?: string): ChainableT<any> {
|
|
const cmdOrCtrl = isMac() ? '{cmd}' : '{ctrl}';
|
|
return cy.get(subject).type(`${cmdOrCtrl}${text}`);
|
|
}
|
|
Cypress.Commands.add('cmdOrCtrlShortcut', {prevSubject: true}, cmdOrCtrlShortcut);
|
|
|
|
// ***********************************************************
|
|
// Post
|
|
// ***********************************************************
|
|
|
|
function postMessage(message: string): ChainableT<any> {
|
|
cy.get('#postListContent').should('be.visible');
|
|
return postMessageAndWait('#post_textbox', message);
|
|
}
|
|
Cypress.Commands.add('postMessage', postMessage);
|
|
|
|
function postMessageReplyInRHS(message: string): ChainableT<any> {
|
|
cy.get('#sidebar-right').should('be.visible');
|
|
return postMessageAndWait('#reply_textbox', message, true);
|
|
}
|
|
Cypress.Commands.add('postMessageReplyInRHS', postMessageReplyInRHS);
|
|
|
|
Cypress.Commands.add('uiPostMessageQuickly', (message) => {
|
|
cy.uiGetPostTextBox().should('be.visible').clear().
|
|
invoke('val', message).wait(TIMEOUTS.HALF_SEC).type(' {backspace}{enter}');
|
|
cy.waitUntil(() => {
|
|
return cy.uiGetPostTextBox().then((el) => {
|
|
return el[0].textContent === '';
|
|
});
|
|
});
|
|
});
|
|
|
|
function postMessageAndWait(textboxSelector: string, message: string, isComment = false) {
|
|
// Add explicit wait to let the page load freely since `cy.get` seemed to block
|
|
// some operation which caused to prolong complete page loading.
|
|
cy.wait(TIMEOUTS.HALF_SEC);
|
|
cy.get(textboxSelector, {timeout: TIMEOUTS.HALF_MIN}).should('be.visible');
|
|
|
|
// # Type then wait for a while for the draft to be saved (async) into the local storage
|
|
cy.get(textboxSelector).clear().type(message).wait(TIMEOUTS.ONE_SEC);
|
|
|
|
// If posting a comment, wait for comment draft from localforage before hitting enter
|
|
if (isComment) {
|
|
waitForCommentDraft(message);
|
|
}
|
|
|
|
cy.get(textboxSelector).should('have.value', message).focus().type('{enter}').wait(TIMEOUTS.HALF_SEC);
|
|
|
|
cy.get(textboxSelector).invoke('val').then((value: string) => {
|
|
if (value.length > 0 && value === message) {
|
|
cy.get(textboxSelector).type('{enter}').wait(TIMEOUTS.HALF_SEC);
|
|
}
|
|
});
|
|
return cy.waitUntil(() => {
|
|
return cy.get(textboxSelector).then((el) => {
|
|
return el[0].textContent === '';
|
|
});
|
|
});
|
|
}
|
|
|
|
interface Draft {
|
|
value?: {
|
|
message?: string;
|
|
};
|
|
}
|
|
|
|
// Wait until comment message is saved as draft from the localforage
|
|
function waitForCommentDraft(message: string) {
|
|
const draftPrefix = 'comment_draft_';
|
|
|
|
cy.waitUntil(async () => {
|
|
// Get all keys from localforage
|
|
const keys = await localforage.keys();
|
|
|
|
// Get all draft comments matching the predefined prefix
|
|
const draftPromises = keys.
|
|
filter((key) => key.includes(draftPrefix)).
|
|
map((key) => localforage.getItem(key));
|
|
const draftItems = await Promise.all(draftPromises) as string[];
|
|
|
|
// Get the exact draft comment
|
|
const commentDraft = draftItems.filter((item) => {
|
|
const draft: Draft = JSON.parse(item);
|
|
|
|
if (draft && draft.value && draft.value.message) {
|
|
return draft.value.message === message;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
return Boolean(commentDraft);
|
|
});
|
|
}
|
|
|
|
function waitUntilPermanentPost() {
|
|
// Add explicit wait to let the page load freely since `cy.get` seemed to block
|
|
// some operation which caused to prolong complete page loading.
|
|
cy.wait(TIMEOUTS.HALF_SEC);
|
|
cy.get('#postListContent', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
|
return cy.waitUntil(() => cy.findAllByTestId('postView').last().then((el) => !(el[0].id.includes(':'))));
|
|
}
|
|
|
|
function getLastPost(): ChainableT<JQuery> {
|
|
waitUntilPermanentPost();
|
|
|
|
return cy.findAllByTestId('postView').last();
|
|
}
|
|
Cypress.Commands.add('getLastPost', getLastPost);
|
|
|
|
function getLastPostId(): ChainableT<string> {
|
|
waitUntilPermanentPost();
|
|
|
|
return cy.findAllByTestId('postView').last().should('have.attr', 'id').and('not.include', ':').
|
|
invoke('replace', /^[^_]*_/, '');
|
|
}
|
|
Cypress.Commands.add('getLastPostId', getLastPostId);
|
|
|
|
function uiWaitUntilMessagePostedIncludes(message: string): ChainableT<any> {
|
|
const checkFn = () => {
|
|
return cy.getLastPost().scrollIntoView().then((el) => {
|
|
const postedMessageEl = el.find('.post-message__text')[0];
|
|
return Boolean(postedMessageEl && postedMessageEl.textContent.includes(message));
|
|
});
|
|
};
|
|
|
|
// Wait for 5 seconds with 500ms check interval
|
|
const options = {
|
|
timeout: TIMEOUTS.FIVE_SEC,
|
|
interval: TIMEOUTS.HALF_SEC,
|
|
errorMsg: `Expected "${message}" to be in the last message posted but not found.`,
|
|
};
|
|
|
|
return cy.waitUntil(checkFn, options);
|
|
}
|
|
Cypress.Commands.add('uiWaitUntilMessagePostedIncludes', uiWaitUntilMessagePostedIncludes);
|
|
|
|
function getNthPostId(index = 0): ChainableT<string> {
|
|
waitUntilPermanentPost();
|
|
|
|
return cy.findAllByTestId('postView').eq(index).should('have.attr', 'id').and('not.include', ':').
|
|
invoke('replace', /^[^_]*_/, '');
|
|
}
|
|
Cypress.Commands.add('getNthPostId', getNthPostId);
|
|
|
|
function uiGetNthPost(index: number): ChainableT<JQuery> {
|
|
waitUntilPermanentPost();
|
|
|
|
return cy.findAllByTestId('postView').eq(index);
|
|
}
|
|
Cypress.Commands.add('uiGetNthPost', uiGetNthPost);
|
|
|
|
function postMessageFromFile(file: string, target = '#post_textbox'): ChainableT<any> {
|
|
return cy.fixture(file, 'utf-8').then((text) => {
|
|
return cy.get(target).clear().invoke('val', text).wait(TIMEOUTS.HALF_SEC).type(' {backspace}{enter}').should('have.text', '');
|
|
});
|
|
}
|
|
Cypress.Commands.add('postMessageFromFile', postMessageFromFile);
|
|
|
|
function compareLastPostHTMLContentFromFile(file: string, timeout = TIMEOUTS.TEN_SEC): ChainableT<any> {
|
|
// * Verify that HTML Content is correct
|
|
return cy.getLastPostId().then((postId) => {
|
|
const postMessageTextId = `#postMessageText_${postId}`;
|
|
|
|
return cy.fixture(file, 'utf-8').then((expectedHtml) => {
|
|
cy.get(postMessageTextId, {timeout}).should('have.html', expectedHtml.replace(/\n$/, ''));
|
|
});
|
|
});
|
|
}
|
|
Cypress.Commands.add('compareLastPostHTMLContentFromFile', compareLastPostHTMLContentFromFile);
|
|
|
|
// ***********************************************************
|
|
// DM
|
|
// ***********************************************************
|
|
|
|
export interface User {
|
|
username: string;
|
|
}
|
|
|
|
function uiGotoDirectMessageWithUser(user: User) {
|
|
// # Open a new direct message with firstDMUser
|
|
cy.uiAddDirectMessage().click().wait(TIMEOUTS.ONE_SEC);
|
|
cy.findByRole('dialog', {name: 'Direct Messages'}).should('be.visible').wait(TIMEOUTS.ONE_SEC);
|
|
|
|
// # Type username
|
|
cy.findByRole('combobox', {name: 'Search for people'}).click({force: true}).
|
|
type(user.username, {force: true}).wait(TIMEOUTS.ONE_SEC);
|
|
|
|
// * Expect user count in the list to be 1
|
|
cy.get('#multiSelectList').
|
|
should('be.visible').
|
|
children().
|
|
should('have.length', 1);
|
|
|
|
// # Select first user in the list
|
|
cy.get('body').
|
|
type('{downArrow}').
|
|
type('{enter}');
|
|
|
|
// # Click on "Go" in the group message's dialog to begin the conversation
|
|
cy.get('#saveItems').click();
|
|
|
|
// * Expect the channel title to be the user's username
|
|
// In the channel header, it seems there is a space after the username, justifying the use of contains.text instead of have.text
|
|
cy.get('#channelHeaderTitle').should('be.visible').and('contain.text', user.username);
|
|
}
|
|
Cypress.Commands.add('uiGotoDirectMessageWithUser', uiGotoDirectMessageWithUser);
|
|
|
|
function sendDirectMessageToUser(user: User, message: string) {
|
|
cy.uiGotoDirectMessageWithUser(user);
|
|
|
|
// # Type message and send it to the user
|
|
cy.postMessage(message);
|
|
}
|
|
Cypress.Commands.add('sendDirectMessageToUser', sendDirectMessageToUser);
|
|
|
|
function sendDirectMessageToUsers(users: User[], message: string) {
|
|
// # Open a new direct message
|
|
cy.uiAddDirectMessage().click();
|
|
|
|
users.forEach((user: User) => {
|
|
// # Type username
|
|
cy.get('#selectItems input').should('be.enabled').type(`@${user.username}`, {force: true});
|
|
|
|
// * Expect user count in the list to be 1
|
|
cy.get('#multiSelectList').
|
|
should('be.visible').
|
|
children().
|
|
should('have.length', 1);
|
|
|
|
// # Select first user in the list
|
|
cy.get('body').
|
|
type('{downArrow}').
|
|
type('{enter}');
|
|
});
|
|
|
|
// # Click on "Go" in the group message's dialog to begin the conversation
|
|
cy.get('#saveItems').click();
|
|
|
|
// * Expect the channel title to be the user's username
|
|
// In the channel header, it seems there is a space after the username, justifying the use of contains.text instead of have.text
|
|
users.forEach((user) => {
|
|
cy.get('#channelHeaderTitle').should('be.visible').and('contain.text', user.username);
|
|
});
|
|
|
|
// # Type message and send it to the user
|
|
cy.postMessage(message);
|
|
}
|
|
Cypress.Commands.add('sendDirectMessageToUsers', sendDirectMessageToUsers);
|
|
|
|
// ***********************************************************
|
|
// Post header
|
|
// ***********************************************************
|
|
|
|
function clickPostHeaderItem(postId: string, location: string, item: string) {
|
|
let idPrefix: string;
|
|
switch (location) {
|
|
case 'CENTER':
|
|
idPrefix = 'post';
|
|
break;
|
|
case 'RHS_ROOT':
|
|
case 'RHS_COMMENT':
|
|
idPrefix = 'rhsPost';
|
|
break;
|
|
case 'SEARCH':
|
|
idPrefix = 'searchResult';
|
|
break;
|
|
|
|
default:
|
|
idPrefix = 'post';
|
|
}
|
|
|
|
if (postId) {
|
|
cy.get(`#${idPrefix}_${postId}`).trigger('mouseover', {force: true}).
|
|
get(`#${location}_${item}_${postId}`).scrollIntoView().trigger('mouseover', {force: true}).click({force: true});
|
|
} else {
|
|
cy.getLastPostId().then((lastPostId) => {
|
|
cy.get(`#${idPrefix}_${lastPostId}`).trigger('mouseover', {force: true}).
|
|
get(`#${location}_${item}_${lastPostId}`).scrollIntoView().trigger('mouseover', {force: true}).click({force: true});
|
|
});
|
|
}
|
|
}
|
|
|
|
function clickPostTime(postId: string, location = 'CENTER') {
|
|
clickPostHeaderItem(postId, location, 'time');
|
|
}
|
|
Cypress.Commands.add('clickPostTime', clickPostTime);
|
|
|
|
function clickPostSaveIcon(postId: string, location = 'CENTER') {
|
|
clickPostHeaderItem(postId, location, 'flagIcon');
|
|
}
|
|
Cypress.Commands.add('clickPostSaveIcon', clickPostSaveIcon);
|
|
|
|
function clickPostDotMenu(postId: string, location = 'CENTER') {
|
|
clickPostHeaderItem(postId, location, 'button');
|
|
}
|
|
Cypress.Commands.add('clickPostDotMenu', clickPostDotMenu);
|
|
|
|
function clickPostReactionIcon(postId: string, location = 'CENTER') {
|
|
clickPostHeaderItem(postId, location, 'reaction');
|
|
}
|
|
Cypress.Commands.add('clickPostReactionIcon', clickPostReactionIcon);
|
|
|
|
function clickPostCommentIcon(postId: string, location = 'CENTER') {
|
|
clickPostHeaderItem(postId, location, 'commentIcon');
|
|
}
|
|
Cypress.Commands.add('clickPostCommentIcon', clickPostCommentIcon);
|
|
|
|
function clickPostActionsMenu(postId: string, location = 'CENTER') {
|
|
clickPostHeaderItem(postId, location, 'actions_button');
|
|
}
|
|
Cypress.Commands.add('clickPostActionsMenu', clickPostActionsMenu);
|
|
|
|
// ***********************************************************
|
|
// Teams
|
|
// ***********************************************************
|
|
|
|
function createNewTeam(teamName: string, teamURL: string) {
|
|
cy.visit('/create_team');
|
|
cy.get('#teamNameInput').type(teamName).type('{enter}');
|
|
cy.get('#teamURLInput').type(teamURL).type('{enter}');
|
|
cy.visit(`/${teamURL}`);
|
|
}
|
|
Cypress.Commands.add('createNewTeam', createNewTeam);
|
|
|
|
function getCurrentTeamURL(siteURL: string): ChainableT<string> {
|
|
let path: string;
|
|
|
|
// siteURL can be provided for cases where subpath is being tested
|
|
if (siteURL) {
|
|
path = window.location.href.substring(siteURL.length);
|
|
} else {
|
|
path = window.location.pathname;
|
|
}
|
|
|
|
const result = path.split('/', 2);
|
|
return cy.wrap(`/${(result[0] ? result[0] : result[1])}`); // sometimes the first element is empty if path starts with '/'
|
|
}
|
|
Cypress.Commands.add('getCurrentTeamURL', getCurrentTeamURL);
|
|
|
|
function leaveTeam() {
|
|
// # Open team menu and click "Leave team"
|
|
cy.uiOpenTeamMenu('Leave team');
|
|
|
|
// * Check that the "leave team modal" opened up
|
|
cy.get('#leaveTeamModal').should('be.visible');
|
|
|
|
// # click on yes
|
|
cy.get('#leaveTeamYes').click();
|
|
|
|
// * Check that the "leave team modal" closed
|
|
cy.get('#leaveTeamModal').should('not.exist');
|
|
}
|
|
Cypress.Commands.add('leaveTeam', leaveTeam);
|
|
|
|
// ***********************************************************
|
|
// Text Box
|
|
// ***********************************************************
|
|
|
|
function clearPostTextbox(channelName = 'town-square') {
|
|
cy.get(`#sidebarItem_${channelName}`).click({force: true});
|
|
cy.uiGetPostTextBox().clear();
|
|
}
|
|
Cypress.Commands.add('clearPostTextbox', clearPostTextbox);
|
|
|
|
// ***********************************************************
|
|
// Min Setting View
|
|
// ************************************************************
|
|
|
|
function minDisplaySettings() {
|
|
cy.get('#themeTitle').should('be.visible', 'contain', 'Theme');
|
|
cy.get('#themeEdit').should('be.visible', 'contain', 'Edit');
|
|
|
|
cy.get('#clockTitle').should('be.visible', 'contain', 'Clock Display');
|
|
cy.get('#clockEdit').should('be.visible', 'contain', 'Edit');
|
|
|
|
cy.get('#name_formatTitle').should('be.visible', 'contain', 'Teammate Name Display');
|
|
cy.get('#name_formatEdit').should('be.visible', 'contain', 'Edit');
|
|
|
|
cy.get('#collapseTitle').should('be.visible', 'contain', 'Default appearance of image previews');
|
|
cy.get('#collapseEdit').should('be.visible', 'contain', 'Edit');
|
|
|
|
cy.get('#message_displayTitle').scrollIntoView().should('be.visible', 'contain', 'Message Display');
|
|
cy.get('#message_displayEdit').should('be.visible', 'contain', 'Edit');
|
|
|
|
cy.get('#languagesTitle').scrollIntoView().should('be.visible', 'contain', 'Language');
|
|
cy.get('#languagesEdit').should('be.visible', 'contain', 'Edit');
|
|
}
|
|
Cypress.Commands.add('minDisplaySettings', minDisplaySettings);
|
|
|
|
// ***********************************************************
|
|
// Change User Status
|
|
// ************************************************************
|
|
|
|
function userStatus(statusInt: number) {
|
|
cy.get('.status-wrapper.status-selector').click();
|
|
cy.get('.MenuItem').eq(statusInt).click();
|
|
}
|
|
Cypress.Commands.add('userStatus', userStatus);
|
|
|
|
// ***********************************************************
|
|
// Channel
|
|
// ************************************************************
|
|
|
|
function getCurrentChannelId(): ChainableT<string> {
|
|
return cy.get('#channel-header', {timeout: TIMEOUTS.HALF_MIN}).invoke('attr', 'data-channelid');
|
|
}
|
|
Cypress.Commands.add('getCurrentChannelId', getCurrentChannelId);
|
|
|
|
function updateChannelHeader(text: string) {
|
|
// # Open channel header dropdown
|
|
cy.get('#channelHeaderDropdownButton').click();
|
|
|
|
// # Click on Channel Settings
|
|
cy.findByText('Channel Settings').should('be.visible').click();
|
|
|
|
// * Verify Channel Settings modal opens
|
|
cy.get('.ChannelSettingsModal').should('be.visible');
|
|
|
|
// # Edit channel header in the modal
|
|
cy.get('#channel_settings_header_textbox').
|
|
should('be.visible').
|
|
clear().
|
|
type(text);
|
|
|
|
// # Save changes
|
|
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
|
|
|
// * Verify changes are saved
|
|
cy.get('.SaveChangesPanel').should('contain', 'Settings saved');
|
|
|
|
// # Close the modal
|
|
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
|
|
|
// * Verify modal is closed
|
|
cy.get('.ChannelSettingsModal').should('not.exist');
|
|
|
|
// Wait for UI to stabilize
|
|
cy.wait(TIMEOUTS.HALF_SEC);
|
|
}
|
|
|
|
Cypress.Commands.add('updateChannelHeader', updateChannelHeader);
|
|
|
|
function updateDMGMChannelHeader(text: string) {
|
|
cy.get('#channelHeaderTitle').
|
|
should('be.visible').
|
|
click();
|
|
cy.get('#channelHeaderDropdownMenu').
|
|
should('be.visible');
|
|
|
|
// * Channel Settings menu option should be visible
|
|
cy.findByText('Edit Header').click();
|
|
cy.get('#edit_textbox').
|
|
clear().
|
|
type(text).
|
|
type('{enter}').
|
|
wait(TIMEOUTS.HALF_SEC);
|
|
}
|
|
|
|
Cypress.Commands.add('updateDMGMChannelHeader', updateDMGMChannelHeader);
|
|
|
|
function checkRunLDAPSync(): ChainableT<any> {
|
|
return cy.apiGetLDAPSync().then((response) => {
|
|
const jobs = response.body;
|
|
const currentTime = new Date();
|
|
|
|
// # Run LDAP Sync if no job exists (or) last status is an error (or) last run time is more than 1 day old
|
|
if (jobs.length === 0 || jobs[0].status === 'error' || ((currentTime.getTime() - (new Date(jobs[0].last_activity_at)).getTime()) > 8640000)) {
|
|
// # Go to system admin LDAP page and run the group sync
|
|
cy.visit('/admin_console/authentication/ldap');
|
|
|
|
// # Click on AD/LDAP Synchronize Now button and verify if succesful
|
|
cy.findByText('AD/LDAP Test').click();
|
|
cy.findByText('AD/LDAP Test Successful').should('be.visible');
|
|
|
|
// # Click on AD/LDAP Synchronize Now button
|
|
cy.findByText('AD/LDAP Synchronize Now').click().wait(TIMEOUTS.ONE_SEC);
|
|
|
|
// * Get the First row
|
|
cy.findByTestId('jobTable').
|
|
find('tbody > tr').
|
|
eq(0).
|
|
as('firstRow');
|
|
|
|
// * Wait until first row updates to say Success
|
|
cy.waitUntil(() => {
|
|
return cy.get('@firstRow').then((el) => {
|
|
return el.find('.status-icon-success').length > 0;
|
|
});
|
|
}
|
|
, {
|
|
timeout: TIMEOUTS.FIVE_MIN,
|
|
interval: TIMEOUTS.TWO_SEC,
|
|
errorMsg: 'AD/LDAP Sync Job did not finish',
|
|
});
|
|
}
|
|
});
|
|
}
|
|
Cypress.Commands.add('checkRunLDAPSync', checkRunLDAPSync);
|
|
|
|
function clickEmojiInEmojiPicker(emojiName: string) {
|
|
cy.get('#emojiPicker').should('exist').and('be.visible').within(() => {
|
|
// # Mouse over the emoji to get it selected
|
|
cy.findAllByTestId(emojiName).eq(0).trigger('mouseover', {force: true});
|
|
|
|
// * Verify that preview shows the emoji selected
|
|
cy.findAllByTestId('emoji_picker_preview').eq(0).should('exist').and('be.visible').contains(emojiName, {matchCase: false});
|
|
|
|
// # Click on the emoji
|
|
cy.findAllByTestId(emojiName).eq(0).click({force: true});
|
|
});
|
|
}
|
|
Cypress.Commands.add('clickEmojiInEmojiPicker', clickEmojiInEmojiPicker);
|
|
|
|
function verifyPostedMessage(message) {
|
|
cy.wait(TIMEOUTS.HALF_SEC).getLastPostId().then((postId) => {
|
|
cy.get(`#post_${postId}`).within(() => {
|
|
cy.get(`#postMessageText_${postId}`).contains(message);
|
|
});
|
|
});
|
|
}
|
|
Cypress.Commands.add('verifyPostedMessage', verifyPostedMessage);
|
|
|
|
function verifyEphemeralMessage(message, isCompactMode, needsToScroll) {
|
|
if (needsToScroll) {
|
|
// # Scroll the ephemeral message into view
|
|
cy.get('#postListContent').within(() => {
|
|
cy.get('.post-list__dynamic').scrollTo('bottom', {ensureScrollable: false});
|
|
});
|
|
}
|
|
|
|
// # Checking if we got the ephemeral message with the selection we made
|
|
cy.wait(TIMEOUTS.HALF_SEC).getLastPostId().then((postId) => {
|
|
cy.get(`#post_${postId}`).within(() => {
|
|
if (isCompactMode) {
|
|
// * Check if Bot message only visible to you and has requisite message.
|
|
cy.get(`#postMessageText_${postId}`).contains(message);
|
|
cy.get(`#postMessageText_${postId}`).contains('(Only visible to you)');
|
|
} else {
|
|
// * Check if Bot message only visible to you
|
|
cy.get('.post__visibility').last().should('exist').and('have.text', '(Only visible to you)');
|
|
|
|
// * Check if we got ephemeral message of our selection
|
|
cy.get(`#postMessageText_${postId}`).contains(message);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
Cypress.Commands.add('verifyEphemeralMessage', verifyEphemeralMessage);
|
|
|
|
declare global {
|
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
namespace Cypress {
|
|
interface Chainable {
|
|
|
|
/**
|
|
* log out user
|
|
*
|
|
* @example
|
|
* cy.logout();
|
|
*/
|
|
logout: typeof logout;
|
|
|
|
/**
|
|
* Wait for a message to get posted as the last post.
|
|
* @returns {string} returns true if found or fail a test if not.
|
|
*
|
|
* @example
|
|
* cy.getCurrentUserId().then((id) => {
|
|
*/
|
|
getCurrentUserId: typeof getCurrentUserId;
|
|
|
|
/**
|
|
* Types `{cmd}` mac / `{ctrl}` windows into post textbox
|
|
*/
|
|
typeCmdOrCtrl: typeof typeCmdOrCtrl;
|
|
|
|
/**
|
|
* Types `{cmd}` mac / `{ctrl}` windows into edit post textbox
|
|
*/
|
|
typeCmdOrCtrlForEdit: typeof typeCmdOrCtrlForEdit;
|
|
|
|
cmdOrCtrlShortcut: typeof cmdOrCtrlShortcut;
|
|
|
|
postMessage: typeof postMessage;
|
|
|
|
postMessageReplyInRHS: typeof postMessageReplyInRHS;
|
|
|
|
/**
|
|
* Wait for a message to get posted as the last post.
|
|
* @param {string} message - message to check if includes in the last post
|
|
* @returns {boolean} returns true if found or fail a test if not.
|
|
*
|
|
* @example
|
|
* const message = 'message';
|
|
* cy.postMessage(message);
|
|
* cy.uiWaitUntilMessagePostedIncludes(message);
|
|
*/
|
|
uiWaitUntilMessagePostedIncludes: typeof uiWaitUntilMessagePostedIncludes;
|
|
|
|
/**
|
|
* Get nth post from the post list
|
|
* @param {number} index - an identifier of a post
|
|
* - zero (0) : oldest post
|
|
* - positive number : from old to latest post
|
|
* - negative number : from new to oldest post
|
|
* @returns {JQuery} response: Cypress-chainable JQuery
|
|
*
|
|
* @example
|
|
* cy.uiGetNthPost(-1);
|
|
*/
|
|
uiGetNthPost: typeof uiGetNthPost;
|
|
|
|
/**
|
|
* Post message via center textbox by directly injected in the textbox
|
|
* @param {string} message - message to be posted
|
|
* @returns void
|
|
*
|
|
* @example
|
|
* cy.uiPostMessageQuickly('Hello world')
|
|
*/
|
|
uiPostMessageQuickly(message: string): ChainableT<void>;
|
|
|
|
/**
|
|
* Clicks on a visible emoji in the emoji picker.
|
|
* For emojis further down the page, search for that emoji in search bar and then use this command to click on it.
|
|
* @param {string} emojiName - The name of emoji to click. For emojis with multiple names concat with ','. eg. slightly_frowning_face
|
|
* @returns void
|
|
*
|
|
* @example
|
|
* cy.uiClickSystemEmoji('slightly_frowning_face');
|
|
* cy.uiClickSystemEmoji('star-struck,grinning_face_with_star_eyes');
|
|
*/
|
|
clickEmojiInEmojiPicker(emojiName: string): ChainableT<void>;
|
|
|
|
/**
|
|
* Get nth post from the post list
|
|
* @returns {JQuery} response: Cypress-chainable JQuery
|
|
*
|
|
* @example
|
|
* cy.getLastPost().then((el: Element) => {;
|
|
*/
|
|
getLastPost: typeof getLastPost;
|
|
|
|
/**
|
|
* Get nth post from the post list
|
|
* @returns {string} response: Cypress-chainable string
|
|
*
|
|
* @example
|
|
* cy.getLastPostId().then((postId) => {
|
|
*/
|
|
getLastPostId: typeof getLastPostId;
|
|
|
|
/**
|
|
* Get post ID based on index of post list
|
|
* zero (0) : oldest post
|
|
* positive number : from old to latest post
|
|
* negative number : from new to oldest post
|
|
*/
|
|
getNthPostId: typeof getNthPostId;
|
|
|
|
/**
|
|
* Post message from a file instantly post a message in a textbox
|
|
* instead of typing into it which takes longer period of time.
|
|
*/
|
|
postMessageFromFile: typeof postMessageFromFile;
|
|
|
|
/**
|
|
* Compares HTML content of a last post against the given file
|
|
* instead of typing into it which takes longer period of time.
|
|
*/
|
|
compareLastPostHTMLContentFromFile: typeof compareLastPostHTMLContentFromFile;
|
|
|
|
/**
|
|
* Go to a DM channel with a given user
|
|
* @param {User} user - the user that should get the message
|
|
* @example
|
|
* const user = {username: 'bob'};
|
|
* cy.uiGotoDirectMessageWithUser(user);
|
|
*/
|
|
uiGotoDirectMessageWithUser(user: User): ChainableT<void>;
|
|
|
|
/**
|
|
* Sends a DM to a given user
|
|
* @param {User} user - the user that should get the message
|
|
* @param {String} message - the message to send
|
|
*/
|
|
sendDirectMessageToUser: typeof sendDirectMessageToUser;
|
|
|
|
/**
|
|
* Sends a GM to a given user list
|
|
* @param {User[]} users - the users that should get the message
|
|
* @param {String} message - the message to send
|
|
*/
|
|
sendDirectMessageToUsers(users: User[], message: string): ChainableT<any>;
|
|
|
|
/**
|
|
* Click post time
|
|
* @param {String} postId - Post ID
|
|
* @param {String} location - as 'CENTER', 'RHS_ROOT', 'RHS_COMMENT', 'SEARCH'
|
|
*/
|
|
clickPostTime(postId: string, location: string): ChainableT<void>;
|
|
|
|
/**
|
|
* Click save icon by post ID or to most recent post (if post ID is not provided)
|
|
* @param {String} postId - Post ID
|
|
* @param {String} [location] - as 'CENTER', 'RHS_ROOT', 'RHS_COMMENT', 'SEARCH'
|
|
*/
|
|
clickPostSaveIcon(postId: string, location?: string): ChainableT<void>;
|
|
|
|
/**
|
|
* Click dot menu by post ID or to most recent post (if post ID is not provided)
|
|
* @param {String} [postId] - Post ID
|
|
* @param {String} [location] - as 'CENTER', 'RHS_ROOT', 'RHS_COMMENT', 'SEARCH'
|
|
*/
|
|
clickPostDotMenu(postId?: string, location?: string): ChainableT<void>;
|
|
|
|
/**
|
|
* Click post reaction icon
|
|
* @param {String} postId - Post ID
|
|
* @param {String} [location] - as 'CENTER', 'RHS_ROOT', 'RHS_COMMENT'
|
|
*/
|
|
clickPostReactionIcon(postId?: string, location?: string): ChainableT<void>;
|
|
|
|
/**
|
|
* Click comment icon by post ID or to most recent post (if post ID is not provided)
|
|
* This open up the RHS
|
|
* @param {String} postId - Post ID
|
|
* @param {String} [location] - as 'CENTER', 'SEARCH'
|
|
*/
|
|
clickPostCommentIcon(postId: string, location?: string): ChainableT<void>;
|
|
|
|
/**
|
|
* Click actions menu by post ID or to most recent post (if post ID is not provided)
|
|
* @param {String} postId - Post ID
|
|
* @param {String} location - as 'CENTER', 'SEARCH'
|
|
*/
|
|
clickPostActionsMenu(postId: string, location?: string): ChainableT<void>;
|
|
|
|
createNewTeam(teamName: string, teamURL: string): ChainableT<void>;
|
|
|
|
getCurrentTeamURL: typeof getCurrentTeamURL;
|
|
|
|
leaveTeam(): ChainableT<void>;
|
|
|
|
clearPostTextbox(channelName: string): ChainableT<void>;
|
|
|
|
/**
|
|
* Checking min setting view for display
|
|
*/
|
|
minDisplaySettings(): ChainableT<void>;
|
|
|
|
/**
|
|
* Set the user's status
|
|
* Need to be in main channel view for this to work
|
|
* 0 = Online
|
|
* 1 = Away
|
|
* 2 = Do Not Disturb
|
|
* 3 = Offline
|
|
*/
|
|
userStatus(statusInt: number): ChainableT<void>;
|
|
|
|
getCurrentChannelId: typeof getCurrentChannelId;
|
|
|
|
/**
|
|
* Update channel header
|
|
* @param {String} text - Text to set the header to
|
|
*/
|
|
updateChannelHeader(text: string): ChainableT<void>;
|
|
|
|
/**
|
|
* Update DM or GM channel header
|
|
* @param {String} text - Text to set the header to
|
|
*/
|
|
updateDMGMChannelHeader(text: string): ChainableT<void>;
|
|
|
|
/**
|
|
* Navigate to system console-PluginManagement from profile settings
|
|
*/
|
|
checkRunLDAPSync: typeof checkRunLDAPSync;
|
|
|
|
/**
|
|
* verifyPostedMessage verifies the receipt of a post containing the given message substring.
|
|
*/
|
|
verifyPostedMessage: typeof verifyPostedMessage;
|
|
|
|
/**
|
|
* verifyEphemeralMessage verifies the receipt of an ephemeral message containing the given
|
|
* message substring. An exact match is avoided to simplify tests.
|
|
*/
|
|
verifyEphemeralMessage: typeof verifyEphemeralMessage;
|
|
}
|
|
}
|
|
}
|