Added Cypress tests and few optimizations (#24592)
* added e2e test * Cleanup * Unused permission * Minor changes * Added case when there is no common team * More tests * nit
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
06765b1f44
Коммит
52072ccf31
@@ -0,0 +1,164 @@
|
||||
// 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 @channel
|
||||
|
||||
describe('Group Message Conversion To Private Channel', () => {
|
||||
let testTeam1;
|
||||
let testTeam2;
|
||||
|
||||
let testUser1;
|
||||
let testUser2;
|
||||
let testUser3;
|
||||
|
||||
let gm;
|
||||
|
||||
before(() => {
|
||||
// we need two teams, and a set of users belonging to both teams
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam1 = team;
|
||||
testUser1 = user;
|
||||
|
||||
cy.apiCreateTeam('gmconversionteam2', 'GM Conversion Team 2').then(({team: team2}) => {
|
||||
testTeam2 = team2;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user: user2}) => {
|
||||
testUser2 = user2;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user: user3}) => {
|
||||
testUser3 = user3;
|
||||
|
||||
const teamMembers1 = [testUser1, testUser2, testUser3].map((u) => ({team_id: testTeam1.id, user_id: u.id}));
|
||||
cy.apiAddUsersToTeam(testTeam1.id, teamMembers1).then(() => {
|
||||
const teamMembers2 = [testUser1, testUser2, testUser3].map((u) => ({team_id: testTeam2.id, user_id: u.id}));
|
||||
cy.apiAddUsersToTeam(testTeam2.id, teamMembers2).then(() => {
|
||||
cy.apiCreateGroupChannel([testUser1.id, testUser2.id, testUser3.id]).then(({channel}) => {
|
||||
gm = channel;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('GM conversion', () => {
|
||||
// Open the GM
|
||||
cy.visit(`/${testTeam1.name}/messages/${gm.name}`);
|
||||
|
||||
// Open the GM conversion dialog
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Convert to Private Channel').click();
|
||||
|
||||
// the dialog has a animation, so we're waiting for it to finish
|
||||
// by waiting for the (currently disabled) confirm button to appear
|
||||
cy.get('.GenericModal__button.delete.disabled').wait(2000);
|
||||
|
||||
// Open the team dropdown and select a team
|
||||
cy.findByText('Select Team').click();
|
||||
cy.findByText(testTeam2.display_name).click();
|
||||
|
||||
// Enter the new channel name and confirm
|
||||
cy.findByPlaceholderText('Channel name').type('Converted Channel');
|
||||
cy.get('.GenericModal__button.delete').click();
|
||||
|
||||
// verify the channel is in a team, as indicated by the presence
|
||||
// of `/channels/` in the URL us `/messages/` in case of GM
|
||||
cy.url().should('contain', `/${testTeam2.name}/channels/converted-channel`);
|
||||
|
||||
// verify the channel created is a private channel by verifying
|
||||
// the presence of the lock icon next to the channel name in LHS
|
||||
cy.get('.SidebarChannel:contains("Converted Channel")').get('.icon.icon-lock-outline').should('be.visible');
|
||||
});
|
||||
|
||||
it('When users belong to only one common team', () => {
|
||||
let testUser4;
|
||||
let gm2;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user}) => {
|
||||
testUser4 = user;
|
||||
|
||||
const teamMembers = [testUser1, testUser2, testUser3, testUser4].map((u) => ({
|
||||
team_id: testTeam2.id,
|
||||
user_id: u.id,
|
||||
}));
|
||||
|
||||
cy.apiAddUsersToTeam(testTeam2.id, teamMembers).then(() => {
|
||||
cy.apiCreateGroupChannel([testUser1.id, testUser2.id, testUser3.id, testUser4.id]).then(({channel}) => {
|
||||
gm2 = channel;
|
||||
|
||||
// Open the GM
|
||||
cy.visit(`/${testTeam1.name}/messages/${gm2.name}`);
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Convert to Private Channel').click();
|
||||
cy.get('.GenericModal__button.delete.disabled').wait(2000);
|
||||
|
||||
cy.contains('Select Team').should('not.exist');
|
||||
|
||||
cy.findByPlaceholderText('Channel name').type('Converted Channel 2');
|
||||
cy.get('.GenericModal__button.delete').click();
|
||||
|
||||
cy.url().should('contain', `/${testTeam2.name}/channels/converted-channel-2`);
|
||||
cy.get('.SidebarChannel:contains("Converted Channel 2")').get('.icon.icon-lock-outline').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('When users have no common team', () => {
|
||||
let testUser5;
|
||||
let gm3;
|
||||
let testTeam3;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user}) => {
|
||||
testUser5 = user;
|
||||
|
||||
cy.apiCreateTeam('gmconversionteam3', 'GM Conversion Team 3').then(({team}) => {
|
||||
testTeam3 = team;
|
||||
console.log(testTeam3);
|
||||
|
||||
const teamMembers = [{
|
||||
team_id: testTeam3.id,
|
||||
user_id: testUser5.id,
|
||||
}];
|
||||
|
||||
cy.apiAddUsersToTeam(testTeam3.id, teamMembers).then(() => {
|
||||
cy.apiCreateGroupChannel([testUser1.id, testUser2.id, testUser3.id, testUser5.id]).then(({channel}) => {
|
||||
gm3 = channel;
|
||||
|
||||
// Open the GM
|
||||
cy.visit(`/${testTeam1.name}/messages/${gm3.name}`);
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Convert to Private Channel').click();
|
||||
cy.findByText('Unable to convert to a channel because group members are part of different teams').wait(2000);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('websocket event should update channel category correctly when in channel', () => {
|
||||
cy.apiCreateGroupChannel([testUser1.id, testUser2.id, testUser3.id]).then(({channel}) => {
|
||||
gm = channel;
|
||||
|
||||
console.log(gm.name);
|
||||
|
||||
// Open the GM
|
||||
cy.visit(`/${testTeam1.name}/messages/${gm.name}`);
|
||||
|
||||
// convert via API call
|
||||
const timestamp = Date.now();
|
||||
cy.apiConvertGMToPrivateChannel(gm.id, testTeam2.id, `Channel ${timestamp}`, `c-${timestamp}`).then(() => {
|
||||
cy.url().should('contain', `${testTeam2.name}/channels/c-${timestamp}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
9
e2e-tests/cypress/tests/support/api/channel.d.ts
поставляемый
9
e2e-tests/cypress/tests/support/api/channel.d.ts
поставляемый
@@ -212,5 +212,14 @@ declare namespace Cypress {
|
||||
* });
|
||||
*/
|
||||
apiCreateArchivedChannel(name: string, displayName: string, type: string, teamId: string, messages?: string[], user?: UserProfile): Chainable<Channel>;
|
||||
|
||||
/**
|
||||
* Command to convert a GM to a private channel
|
||||
* @param {string} channelId - channel id of GM to be converted
|
||||
* @param {string} teamId - id of team to move the converted private channel to
|
||||
* @param {string} displayName - display name of converted channel
|
||||
* @param {string} name - name of converted channel
|
||||
*/
|
||||
apiConvertGMToPrivateChannel(channelId: string, teamId: string, displayName: string, name: string): Chainable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,3 +186,19 @@ Cypress.Commands.add('apiCreateArchivedChannel', (name, displayName, type = 'O',
|
||||
return cy.wrap(channel);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('apiConvertGMToPrivateChannel', (channelId, teamId, displayName, name) => {
|
||||
const body = {
|
||||
channel_id: channelId,
|
||||
team_id: teamId,
|
||||
display_name: displayName,
|
||||
name,
|
||||
};
|
||||
|
||||
return cy.request({
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
url: `/api/v4/channels/${channelId}/convert_to_channel?team_id=${teamId}`,
|
||||
method: 'POST',
|
||||
body,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, {useRef, useState} from 'react';
|
||||
import React, {useCallback, useRef, useState} from 'react';
|
||||
import type {CSSProperties} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import ReactSelect, {components} from 'react-select';
|
||||
@@ -81,7 +81,7 @@ const Option = (props: any) => {
|
||||
};
|
||||
|
||||
const DropdownInput = <T extends ValueType>(props: Props<T>) => {
|
||||
const {value, placeholder, className, addon, name, textPrefix, legend, onChange, styles, options, error, testId, ...otherProps} = props;
|
||||
const {value, placeholder, className, addon, name, textPrefix, legend, onChange, styles, options, error, testId, required, ...otherProps} = props;
|
||||
|
||||
const [focused, setFocused] = useState(false);
|
||||
|
||||
@@ -99,30 +99,29 @@ const DropdownInput = <T extends ValueType>(props: Props<T>) => {
|
||||
const [customInputLabel, setCustomInputLabel] = useState<CustomMessageInputType>(null);
|
||||
const ownValue = useRef<T>();
|
||||
|
||||
const ownOnChange = (value: T, action: ActionMeta<T>) => {
|
||||
const ownOnChange = useCallback((value: T, action: ActionMeta<T>) => {
|
||||
ownValue.current = value;
|
||||
onChange(value, action);
|
||||
};
|
||||
const validateInput = () => {
|
||||
if (!props.required || (ownValue.current !== null && ownValue.current)) {
|
||||
}, [onChange]);
|
||||
|
||||
const validateInput = useCallback(() => {
|
||||
if (!required || (ownValue.current !== null && ownValue.current)) {
|
||||
setCustomInputLabel(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const validationErrorMsg = formatMessage({id: 'widget.input.required', defaultMessage: 'This field is required'});
|
||||
setCustomInputLabel({type: ItemStatus.ERROR, value: validationErrorMsg});
|
||||
};
|
||||
|
||||
const onInputBlur = (event: React.FocusEvent<HTMLElement>) => {
|
||||
const {onBlur} = props;
|
||||
}, [required, formatMessage]);
|
||||
|
||||
const onInputBlur = useCallback((event: React.FocusEvent<HTMLElement>) => {
|
||||
setFocused(false);
|
||||
validateInput();
|
||||
|
||||
if (onBlur) {
|
||||
onBlur(event);
|
||||
if (otherProps.onBlur) {
|
||||
otherProps.onBlur(event);
|
||||
}
|
||||
};
|
||||
}, [otherProps.onBlur, validateInput]);
|
||||
|
||||
const showLegend = Boolean(focused || value);
|
||||
const isError = error || customInputLabel?.type === 'error';
|
||||
|
||||
Ссылка в новой задаче
Block a user