MM61173 - settings modal base creation (#30338)
* 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>
Этот коммит содержится в:
@@ -88,12 +88,14 @@ describe('Verify Accessibility Support in different sections in Settings and Pro
|
||||
});
|
||||
|
||||
it('MM-T1465_1 Verify Label & Tab behavior in section links', () => {
|
||||
// * Verify aria-label and tab support in section of Account settings modal
|
||||
// * Verify tab selection and keyboard navigation in Account settings modal
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
cy.findByRole('tab', {name: 'profile settings'}).should('be.visible').focus().should('be.focused');
|
||||
['profile settings', 'security'].forEach((text) => {
|
||||
// * Verify aria-label on each tab and it supports navigating to the next tab with arrow keys
|
||||
cy.focused().should('have.attr', 'aria-label', text).type('{downarrow}');
|
||||
// * Verify each tab is correctly selected and supports navigating to the next tab with arrow keys
|
||||
cy.findByRole('tab', {name: text}).
|
||||
should('have.attr', 'aria-selected', 'true').
|
||||
type('{downarrow}');
|
||||
});
|
||||
cy.uiClose();
|
||||
|
||||
@@ -101,8 +103,10 @@ describe('Verify Accessibility Support in different sections in Settings and Pro
|
||||
cy.uiOpenSettingsModal();
|
||||
cy.findByRole('tab', {name: 'notifications'}).should('be.visible').focus().should('be.focused');
|
||||
['notifications', 'display', 'sidebar', 'advanced'].forEach((text) => {
|
||||
// * Verify aria-label on each tab and it supports navigating to the next tab with arrow keys
|
||||
cy.focused().should('have.attr', 'aria-label', text).type('{downarrow}');
|
||||
// * Verify each tab is correctly selected and supports navigating to the next tab with arrow keys
|
||||
cy.findByRole('tab', {name: text}).
|
||||
should('have.attr', 'aria-selected', 'true').
|
||||
type('{downarrow}');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -29,30 +29,39 @@ describe('Channel Settings - Channel Header', () => {
|
||||
// # Visit the newly created channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Click on the channel name in the channel header to open the channel menu options
|
||||
cy.get(`[aria-label="${channel.display_name.split('-').join(' ').toLowerCase()} channel menu"]`).click();
|
||||
|
||||
// # Select the "Edit Channel Header" option from the dropdown
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||
cy.findByText('Edit Channel Header').click();
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// * Verify modal is open
|
||||
cy.get('.ChannelSettingsModal').should('be.visible');
|
||||
cy.get('#genericModalLabel').should('contain', 'Channel Settings');
|
||||
|
||||
// # Type something in the header edit box
|
||||
cy.get('textarea[placeholder="Edit the Channel Header..."]').clear().type('This is the new header content');
|
||||
cy.get('#channel_settings_header_textbox').clear().type('This is the new header content');
|
||||
|
||||
// * Verify the "Preview" button exists
|
||||
cy.findByText('Preview').should('be.visible');
|
||||
// * Verify the "Preview" button exists and is not active
|
||||
cy.get('#channel_settings_header_textbox').
|
||||
parents('.AdvancedTextbox').
|
||||
find('#PreviewInputTextButton').
|
||||
should('not.have.class', 'active');
|
||||
|
||||
// * Verify that before hitting the preview button, the style on the textbox is `display: block`
|
||||
cy.get('textarea[placeholder="Edit the Channel Header..."]').should('have.css', 'display', 'block');
|
||||
cy.get('#channel_settings_header_textbox').should('have.css', 'display', 'block');
|
||||
|
||||
// # Click the "Preview" button
|
||||
cy.findByText('Preview').click();
|
||||
|
||||
// * Verify the "Preview" button label has changed to "Edit"
|
||||
cy.findByText('Edit').should('be.visible');
|
||||
cy.get('#channel_settings_header_textbox').
|
||||
parents('.AdvancedTextbox').
|
||||
find('#PreviewInputTextButton').click();
|
||||
|
||||
// * Verify that the display is now none on the textbox element
|
||||
cy.get('textarea[placeholder="Edit the Channel Header..."]').should('have.css', 'display', 'none');
|
||||
cy.get('#channel_settings_header_textbox').should('have.css', 'display', 'none');
|
||||
|
||||
// * Verify the "Preview" button has class active
|
||||
cy.get('#channel_settings_header_textbox').
|
||||
parents('.AdvancedTextbox').
|
||||
find('#PreviewInputTextButton').
|
||||
should('have.class', 'active');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
// 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 @channel_settings
|
||||
|
||||
import {Team} from '@mattermost/types/teams';
|
||||
import {Channel} from '@mattermost/types/channels';
|
||||
import {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
describe('Channel Settings Modal', () => {
|
||||
let testTeam: Team;
|
||||
let testUser: UserProfile;
|
||||
let testChannel: Channel;
|
||||
let originalTestChannel: Channel;
|
||||
|
||||
before(() => {
|
||||
// Setup test data
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create a test channel
|
||||
cy.apiCreateChannel(testTeam.id, 'test-channel', 'Test Channel').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
originalTestChannel = {...channel};
|
||||
cy.apiAddUserToChannel(channel.id, user.id);
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Visit the channel before each test
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
});
|
||||
|
||||
// 2DO: update tests ids once QA has defined the test plan and zephyr tests
|
||||
it('MM-T1: Can open and close the channel settings modal', () => {
|
||||
// # Open channel settings modal from channel header dropdown
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// * Verify modal is open
|
||||
cy.get('.ChannelSettingsModal').should('be.visible');
|
||||
cy.get('#genericModalLabel').should('contain', 'Channel Settings');
|
||||
|
||||
// # Close the modal
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
|
||||
// * Verify modal is closed
|
||||
cy.get('.ChannelSettingsModal').should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T2: Can navigate between tabs', () => {
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// * Verify Info tab is active by default
|
||||
cy.get('#infoButton').should('have.class', 'active');
|
||||
cy.get('.ChannelSettingsModal__infoTab').should('be.visible');
|
||||
|
||||
// # Click on Archive tab
|
||||
cy.get('#archiveButton').click();
|
||||
|
||||
// * Verify Archive tab is active
|
||||
cy.get('#archiveButton').should('have.class', 'active');
|
||||
cy.get('.ChannelSettingsModal__archiveTab').should('be.visible');
|
||||
|
||||
// # Click back on Info tab
|
||||
cy.get('#infoButton').click();
|
||||
|
||||
// * Verify Info tab is active again
|
||||
cy.get('#infoButton').should('have.class', 'active');
|
||||
cy.get('.ChannelSettingsModal__infoTab').should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T3: Can edit channel name and URL', () => {
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Get original channel name, url and alias it
|
||||
cy.get('#input_channel-settings-name').invoke('val').as('originalName');
|
||||
cy.get('.url-input-label').invoke('val').as('originalUrl');
|
||||
|
||||
// # Edit channel name
|
||||
cy.get('#input_channel-settings-name').clear().type('Updated Channel Name');
|
||||
|
||||
// * Verify URL is updated automatically
|
||||
cy.get('.url-input-label').should('contain', 'updated-channel-name');
|
||||
|
||||
// # Click Save
|
||||
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 channel header shows updated name
|
||||
cy.get('#channelHeaderTitle').should('contain', 'Updated Channel Name');
|
||||
|
||||
// # Open channel settings modal to restore original values
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// * Verify the channel name is reset to the original value
|
||||
cy.get('#input_channel-settings-name').clear().type(originalTestChannel.display_name);
|
||||
|
||||
// # Try to change URL to match the original url
|
||||
cy.get('.url-input-button').click();
|
||||
cy.get('.url-input-container input').clear().type(originalTestChannel.name);
|
||||
cy.get('.url-input-container button.url-input-button').click();
|
||||
|
||||
// # 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 channel header shows updated name
|
||||
cy.get('#channelHeaderTitle').should('contain', originalTestChannel.display_name);
|
||||
});
|
||||
|
||||
it('MM-T4: Shows error for invalid channel name', () => {
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Set empty channel name
|
||||
cy.get('#input_channel-settings-name').clear();
|
||||
|
||||
// # Try Save changes
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// * Verify error is shown
|
||||
cy.get('.Input_fieldset').should('have.class', 'Input_fieldset___error');
|
||||
cy.get('.SaveChangesPanel').should('contain', 'There are errors in the form above');
|
||||
});
|
||||
|
||||
it('MM-T6: Can edit channel purpose and header', () => {
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Edit channel purpose
|
||||
cy.get('#channel_settings_purpose_textbox').clear().type('This is a test purpose');
|
||||
|
||||
// # Edit channel header
|
||||
cy.get('#channel_settings_header_textbox').clear().type('This is a test header');
|
||||
|
||||
// # 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 channel header shows updated header
|
||||
cy.get('#channelHeaderDescription').should('contain', 'This is a test header');
|
||||
});
|
||||
|
||||
it('MM-T7: Shows error when purpose exceeds character limit', () => {
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Enter purpose that exceeds character limit (250 characters)
|
||||
const longPurpose = 'a'.repeat(260);
|
||||
cy.get('#channel_settings_purpose_textbox').clear().type(longPurpose);
|
||||
|
||||
// * Verify error is shown
|
||||
cy.findAllByTestId('channel_settings_purpose_textbox').should('have.class', 'textarea--has-errors');
|
||||
cy.get('.SaveChangesPanel').should('contain', 'There are errors in the form above');
|
||||
});
|
||||
|
||||
it('MM-T8: Shows error when header exceeds character limit', () => {
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Enter header that exceeds character limit (1024 characters)
|
||||
const longHeader = 'a'.repeat(1050);
|
||||
cy.findByTestId('channel_settings_header_textbox').clear().type(longHeader);
|
||||
|
||||
// * Verify error is shown
|
||||
cy.findByTestId('channel_settings_header_textbox').should('have.class', 'textarea--has-errors');
|
||||
cy.get('.SaveChangesPanel').should('contain', 'There are errors in the form above');
|
||||
});
|
||||
|
||||
it('MM-T9: Can archive a channel and redirect to previous visited channel', () => {
|
||||
// # Create a new channel for this test
|
||||
cy.apiCreateChannel(testTeam.id, 'first-channel', 'First Channel').then(({channel: channel1}) => {
|
||||
cy.apiCreateChannel(testTeam.id, 'second-channel', 'Second Channel').then(({channel}) => {
|
||||
// # visit town square
|
||||
cy.visit(`/${testTeam.name}/channels/${channel1.name}`);
|
||||
|
||||
// # visit just created channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Click on Archive tab
|
||||
cy.get('#archiveButton').click();
|
||||
|
||||
// # Click Archive button
|
||||
cy.get('#channelSettingsArchiveChannelButton').click();
|
||||
|
||||
// * Verify confirmation modal appears
|
||||
cy.get('#archiveChannelConfirmModal').should('be.visible');
|
||||
|
||||
// # Confirm archive
|
||||
cy.findByRole('button', {name: 'Confirm'}).click();
|
||||
|
||||
// * Verify redirect to Town Square
|
||||
cy.url().should('include', channel1.name);
|
||||
|
||||
// * Verify channel is no longer in sidebar
|
||||
cy.get('.SidebarChannel').contains('Archive Test').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T10: Warns when switching tabs with unsaved changes', () => {
|
||||
// # Create a new channel for this test
|
||||
cy.apiCreateChannel(testTeam.id, 'unsaved-test', 'Unsaved Test').then(({channel}) => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Make a change to the channel name
|
||||
cy.get('#input_channel-settings-name').clear().type('Changed Name');
|
||||
|
||||
// # Try to switch to Archive tab
|
||||
cy.get('#archiveButton').click();
|
||||
|
||||
// * Verify we're still on the Info tab due to unsaved changes
|
||||
cy.get('#infoButton').should('have.class', 'active');
|
||||
|
||||
// * Verify save changes panel shows error
|
||||
cy.get('.SaveChangesPanel').should('have.class', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
// MM-T11: Can reset changes without saving
|
||||
it('MM-T11: Can reset changes without saving', () => {
|
||||
// # Create a new channel for this test
|
||||
cy.apiCreateChannel(testTeam.id, 'reset-test', 'Reset Test').then(({channel}) => {
|
||||
// # Visit the channel page using the channel name returned from the API
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Get original channel name and alias it
|
||||
cy.get('#input_channel-settings-name').invoke('val').as('originalName');
|
||||
|
||||
// # Change the channel name
|
||||
cy.get('#input_channel-settings-name').clear().type('Temporary Name');
|
||||
|
||||
// # Click the Reset button in the SaveChangesPanel
|
||||
cy.get('.SaveChangesPanel button').contains('Reset').click();
|
||||
|
||||
// * Verify the channel name is reset to the original value
|
||||
cy.get('@originalName').then((originalName) => {
|
||||
cy.get('#input_channel-settings-name').should('have.value', originalName);
|
||||
});
|
||||
|
||||
// * Verify the SaveChangesPanel is no longer visible
|
||||
cy.get('.SaveChangesPanel').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
// MM-T12: Can preview purpose and header with markdown
|
||||
it('MM-T12: Can preview purpose and header with markdown', () => {
|
||||
// # Create a new channel for this test
|
||||
cy.apiCreateChannel(testTeam.id, 'markdown-test', 'Markdown Test').then(({channel}) => {
|
||||
// # Visit the channel page using the channel name returned from the API
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Add markdown to purpose
|
||||
cy.get('#channel_settings_purpose_textbox').clear().type('This is **bold** and _italic_ text');
|
||||
|
||||
// # Click preview button for purpose (assumes preview button is inside the AdvancedTextbox container)
|
||||
cy.get('#channel_settings_purpose_textbox').
|
||||
parents('.AdvancedTextbox').
|
||||
find('#PreviewInputTextButton').
|
||||
click();
|
||||
|
||||
// * Verify that markdown is rendered in preview: check for bold and italic formatting
|
||||
cy.get('.textbox-preview-area').
|
||||
should('contain', 'This is').
|
||||
find('strong').
|
||||
should('contain', 'bold');
|
||||
cy.get('.textbox-preview-area').
|
||||
find('em').
|
||||
should('contain', 'italic');
|
||||
|
||||
// # Add markdown to header
|
||||
cy.get('#channel_settings_header_textbox').clear().type('Visit [Mattermost](https://mattermost.com)');
|
||||
|
||||
// # Click preview button for header
|
||||
cy.get('#channel_settings_header_textbox').
|
||||
parents('.AdvancedTextbox').
|
||||
find('#PreviewInputTextButton').
|
||||
click();
|
||||
|
||||
// * Verify that markdown is rendered in preview: check for a link with text "Mattermost"
|
||||
cy.get('.textbox-preview-area').
|
||||
should('contain', 'Visit').
|
||||
find('a').
|
||||
should('contain', 'Mattermost');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T13: Validates URL when editing channel name', () => {
|
||||
// # Create two channels to test URL conflict error
|
||||
cy.apiCreateChannel(testTeam.id, 'first-channel', 'First Channel').then(({channel: channel1}) => {
|
||||
cy.apiCreateChannel(testTeam.id, 'second-channel', 'Second Channel').then(({channel}) => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Try to change URL to match the first channel
|
||||
cy.get('.url-input-button').click();
|
||||
cy.get('.url-input-container input').clear().type(channel1.name);
|
||||
cy.get('.url-input-container button.url-input-button').click();
|
||||
|
||||
// * Verify error is shown
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
cy.get('.SaveChangesPanel').should('contain', 'There are errors in the form above');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -37,22 +37,23 @@ describe('Channel Settings', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Go to channel dropdown > Rename channel
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||
cy.findByText('Rename Channel').click();
|
||||
// # Go to channel dropdown > Channel Settings Modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Try to enter existing URL and save
|
||||
cy.get('#channel_name').clear().type('town-square');
|
||||
cy.get('#save-button').click();
|
||||
cy.get('#input_channel-settings-name').clear().type('town-square');
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// # Error is displayed and URL is unchanged
|
||||
cy.get('.has-error').should('be.visible').and('contain', 'A channel with that name already exists on the same team.');
|
||||
cy.get('.SaveChangesPanel').should('contain', 'There are errors in the form above');
|
||||
cy.get('.url-input-error').should('be.visible').and('contain.text', 'A channel with that name already exists on the same team.');
|
||||
|
||||
cy.url().should('include', `/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Enter a new URL and save
|
||||
cy.get('#channel_name').clear().type('another-town-square');
|
||||
cy.get('#save-button').click();
|
||||
cy.get('#input_channel-settings-name').clear().type('another-town-square');
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// * URL is updated and no errors are displayed
|
||||
cy.url().should('include', `/${testTeam.name}/channels/another-town-square`);
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Team} from '@mattermost/types/teams';
|
||||
import {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] 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('Channels', () => {
|
||||
let testUser: UserProfile;
|
||||
let testTeam: Team;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3348 - Convert to private channel should only be shown to users with permission', () => {
|
||||
// # Reset permissions to default
|
||||
resetPermissionsToDefault();
|
||||
|
||||
// # Enable convert public channels to private for all users
|
||||
enablePermission('all_users-public_channel-convert_public_channel_to_private-checkbox');
|
||||
saveConfig();
|
||||
|
||||
// # Login as a regular user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create new test channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Click the channel header dropdown
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// open ChannelSettings submenu
|
||||
cy.findByRole('menuitem', {name: 'Channel Settings'}).trigger('mouseover');
|
||||
|
||||
// * Channel convert to private should be visible and confirm
|
||||
cy.get('#channelConvertToPrivate').should('be.visible').click();
|
||||
cy.findByTestId('convertChannelConfirm').should('be.visible').click();
|
||||
|
||||
// # Click the channel header dropdown
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// open ChannelSettings submenu
|
||||
cy.findByRole('menuitem', {name: 'Channel Settings'}).trigger('mouseover');
|
||||
|
||||
// * Channel convert to private should no longer be visible
|
||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
||||
});
|
||||
|
||||
// # Reset permissions to default
|
||||
resetPermissionsToDefault();
|
||||
|
||||
// # Login as a regular user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create new test channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Click the channel header dropdown
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// open ChannelSettings submenu
|
||||
cy.findByRole('menuitem', {name: 'Channel Settings'}).trigger('mouseover');
|
||||
|
||||
// * Channel convert to private should no longer be visible
|
||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
||||
});
|
||||
|
||||
// # Reset permissions to default
|
||||
resetPermissionsToDefault();
|
||||
|
||||
// # Remove permission from team admins
|
||||
removePermission('team_admin-public_channel-convert_public_channel_to_private-checkbox');
|
||||
saveConfig();
|
||||
|
||||
// # Promote user to team admin
|
||||
cy.apiUpdateTeamMemberSchemeRole(testTeam.id, testUser.id, {scheme_admin: true, scheme_user: true});
|
||||
|
||||
// # Login as the now team admin user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create new test channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Click the channel header dropdown
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// open ChannelSettings submenu
|
||||
cy.findByRole('menuitem', {name: 'Channel Settings'}).trigger('mouseover');
|
||||
|
||||
// * Channel convert to private should not be visible
|
||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
||||
});
|
||||
|
||||
// # Reset permissions to default
|
||||
resetPermissionsToDefault();
|
||||
|
||||
// # Login as the team admin user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create new test channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Click the channel header dropdown
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// open ChannelSettings submenu
|
||||
cy.findByRole('menuitem', {name: 'Channel Settings'}).trigger('mouseover');
|
||||
|
||||
// * Channel convert to private should be visible and confirm
|
||||
cy.get('#channelConvertToPrivate').should('be.visible').click();
|
||||
cy.findByTestId('convertChannelConfirm').should('be.visible').click();
|
||||
|
||||
// # Click the channel header dropdown
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// open ChannelSettings submenu
|
||||
cy.findByRole('menuitem', {name: 'Channel Settings'}).trigger('mouseover');
|
||||
|
||||
// * Channel convert to private should no longer be visible
|
||||
cy.get('#channelConvertToPrivate').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const saveConfig = () => {
|
||||
cy.get('#saveSetting').click();
|
||||
cy.waitUntil(() => cy.get('#saveSetting').then((el) => {
|
||||
return el[0].innerText === 'Save';
|
||||
}));
|
||||
};
|
||||
|
||||
const enablePermission = (permissionCheckBoxTestId) => {
|
||||
cy.findByTestId(permissionCheckBoxTestId).then((el) => {
|
||||
if (!el.hasClass('checked')) {
|
||||
el.click();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const removePermission = (permissionCheckBoxTestId) => {
|
||||
cy.findByTestId(permissionCheckBoxTestId).then((el) => {
|
||||
if (el.hasClass('checked')) {
|
||||
el.click();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const resetPermissionsToDefault = () => {
|
||||
// # Login as sysadmin and navigate to system scheme page
|
||||
cy.apiAdminLogin();
|
||||
cy.visit('/admin_console/user_management/permissions/system_scheme');
|
||||
|
||||
// # Click reset to defaults and confirm
|
||||
cy.findByTestId('resetPermissionsToDefault').click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
// # Save
|
||||
saveConfig();
|
||||
};
|
||||
@@ -0,0 +1,567 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Team} from '@mattermost/types/teams';
|
||||
import {UserProfile} from '@mattermost/types/users';
|
||||
import {Channel} from '@mattermost/types/channels';
|
||||
import {getAdminAccount} from '../../../support/env';
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] 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('Channel Type Conversion (Public to Private Only)', () => {
|
||||
let testUser: UserProfile;
|
||||
let testTeam: Team;
|
||||
const admin = getAdminAccount();
|
||||
|
||||
// Helper functions for permission management
|
||||
const saveConfig = () => {
|
||||
cy.waitUntil(() =>
|
||||
cy.get('#saveSetting').then((button) => {
|
||||
// Check if the button text is exactly "Save"
|
||||
if (button.text().trim() === 'Save') {
|
||||
button.click();
|
||||
return true; // signals waitUntil that it succeeded
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const enablePermission = (permissionCheckBoxTestId: string) => {
|
||||
cy.findByTestId(permissionCheckBoxTestId).then((el) => {
|
||||
if (!el.hasClass('checked')) {
|
||||
el.click();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const removePermission = (permissionCheckBoxTestId: string) => {
|
||||
cy.findByTestId(permissionCheckBoxTestId).then((el) => {
|
||||
if (el.hasClass('checked')) {
|
||||
el.click();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const promoteToChannelAdmin = (userId, channelId, admin) => {
|
||||
cy.externalRequest({
|
||||
user: admin,
|
||||
method: 'put',
|
||||
path: `channels/${channelId}/members/${userId}/schemeRoles`,
|
||||
data: {
|
||||
scheme_user: true,
|
||||
scheme_admin: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const resetPermissionsToDefault = () => {
|
||||
// # Login as sysadmin and navigate to system scheme page
|
||||
cy.apiAdminLogin();
|
||||
cy.visit('/admin_console/user_management/permissions/system_scheme');
|
||||
|
||||
// # Click reset to defaults and confirm
|
||||
cy.findByTestId('resetPermissionsToDefault').click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
// # Save
|
||||
saveConfig();
|
||||
cy.wait(1000);
|
||||
cy.visit('/admin_console/user_management/permissions/system_scheme');
|
||||
};
|
||||
|
||||
// Helper functions for permission setup
|
||||
const setupPermissions = (config: {
|
||||
resetToDefault?: boolean;
|
||||
publicToPrivate?: boolean;
|
||||
removeFromTeamAdmin?: boolean;
|
||||
}) => {
|
||||
if (config.resetToDefault) {
|
||||
resetPermissionsToDefault();
|
||||
}
|
||||
|
||||
// Public to private conversion permissions
|
||||
if (config.publicToPrivate) {
|
||||
enablePermission('all_users-public_channel-convert_public_channel_to_private-checkbox');
|
||||
} else if (config.publicToPrivate === false) {
|
||||
removePermission('all_users-public_channel-convert_public_channel_to_private-checkbox');
|
||||
}
|
||||
|
||||
// Remove public to private conversion from team admin if specified
|
||||
if (config.removeFromTeamAdmin) {
|
||||
removePermission('team_admin-public_channel-convert_public_channel_to_private-checkbox');
|
||||
}
|
||||
|
||||
cy.wait(1000);
|
||||
|
||||
saveConfig();
|
||||
};
|
||||
|
||||
// Helper functions for channel management
|
||||
const createAndVisitPublicChannel = (teamName: string, channelId: string, displayName: string): Cypress.Chainable<Channel> => {
|
||||
return cy.apiCreateChannel(testTeam.id, channelId, displayName).then(({channel}) => {
|
||||
cy.apiAddUserToChannel(channel.id, testUser.id);
|
||||
cy.visit(`/${teamName}/channels/${channel.name}`);
|
||||
return cy.wrap(channel);
|
||||
});
|
||||
};
|
||||
|
||||
const createAndVisitPrivateChannel = (teamName: string, channelId: string, displayName: string): Cypress.Chainable<Channel> => {
|
||||
return cy.apiCreateChannel(testTeam.id, channelId, displayName, 'P').then(({channel}) => {
|
||||
cy.apiAddUserToChannel(channel.id, testUser.id);
|
||||
cy.visit(`/${teamName}/channels/${channel.name}`);
|
||||
return cy.wrap(channel);
|
||||
});
|
||||
};
|
||||
|
||||
const visitChannel = (teamName: string, channelName: string) => {
|
||||
cy.visit(`/${teamName}/channels/${channelName}`);
|
||||
};
|
||||
|
||||
// Helper functions for UI interaction
|
||||
const openChannelSettingsModal = () => {
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
cy.get('.ChannelSettingsModal').should('be.visible');
|
||||
};
|
||||
|
||||
const closeChannelSettingsModal = () => {
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
cy.get('.ChannelSettingsModal').should('not.exist');
|
||||
};
|
||||
|
||||
// Helper functions for UI interaction
|
||||
const saveChannelSettings = () => {
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
};
|
||||
|
||||
const verifySettingsSaved = () => {
|
||||
cy.get('.SaveChangesPanel').should('contain', 'Settings saved');
|
||||
};
|
||||
|
||||
// Helper functions for channel conversion
|
||||
const convertChannelToPrivate = () => {
|
||||
// Select private channel type
|
||||
cy.get('#public-private-selector-button-P').click();
|
||||
cy.get('#public-private-selector-button-P').should('have.class', 'selected');
|
||||
|
||||
// Save changes - this will trigger the confirmation modal
|
||||
saveChannelSettings();
|
||||
|
||||
// Handle the confirmation modal that appears when converting from public to private
|
||||
cy.get('#confirmModal').should('be.visible');
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
// Verify settings were saved
|
||||
verifySettingsSaved();
|
||||
};
|
||||
|
||||
// Function kept for potential future use but not used in current tests
|
||||
// since private to public conversion is no longer allowed
|
||||
// const convertChannelToPublic = () => {
|
||||
// cy.get('#public-private-selector-button-O').click();
|
||||
// cy.get('#public-private-selector-button-O').should('have.class', 'selected');
|
||||
// };
|
||||
|
||||
// Helper functions for verification
|
||||
const verifyChannelIsPrivate = (channelName: string) => {
|
||||
cy.get('.SidebarChannel').contains(channelName).parent().find('.icon-lock-outline').should('exist');
|
||||
};
|
||||
|
||||
// Function kept for potential future use but not used in current tests
|
||||
// since private to public conversion is no longer allowed
|
||||
// const verifyChannelIsPublic = (channelName: string) => {
|
||||
// cy.get('.SidebarChannel').contains(channelName).parent().find('.icon-lock-outline').should('not.exist');
|
||||
// };
|
||||
|
||||
const verifyConversionOptionDisabled = (toPrivate = true) => {
|
||||
if (toPrivate) {
|
||||
// Wait for the UI to fully load and stabilize
|
||||
cy.wait(500);
|
||||
|
||||
// Check if the button is disabled - it might have a disabled attribute or a disabled class
|
||||
cy.get('#public-private-selector-button-P').then(($el) => {
|
||||
const isDisabled = $el.hasClass('disabled') || $el.prop('disabled') === true || $el.attr('aria-disabled') === 'true';
|
||||
expect(isDisabled).to.be.true;
|
||||
});
|
||||
} else {
|
||||
// Wait for the UI to fully load and stabilize
|
||||
cy.wait(500);
|
||||
|
||||
// Check if the button is disabled - it might have a disabled attribute or a disabled class
|
||||
cy.get('#public-private-selector-button-O').then(($el) => {
|
||||
const isDisabled = $el.hasClass('disabled') || $el.prop('disabled') === true || $el.attr('aria-disabled') === 'true';
|
||||
expect(isDisabled).to.be.true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const verifyConversionOptionEnabled = (toPrivate = true) => {
|
||||
if (toPrivate) {
|
||||
// Wait for the UI to fully load and stabilize
|
||||
cy.wait(500);
|
||||
|
||||
// Check if the button is enabled - it should not have disabled attributes or classes
|
||||
cy.get('#public-private-selector-button-P').then(($el) => {
|
||||
const isDisabled = $el.hasClass('disabled') || $el.prop('disabled') === true || $el.attr('aria-disabled') === 'true';
|
||||
expect(isDisabled).to.be.false;
|
||||
});
|
||||
} else {
|
||||
// Wait for the UI to fully load and stabilize
|
||||
cy.wait(500);
|
||||
|
||||
// Check if the button is enabled - it should not have disabled attributes or classes
|
||||
cy.get('#public-private-selector-button-O').then(($el) => {
|
||||
const isDisabled = $el.hasClass('disabled') || $el.prop('disabled') === true || $el.attr('aria-disabled') === 'true';
|
||||
expect(isDisabled).to.be.false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to make user a channel admin
|
||||
const makeUserChannelAdmin = (channelId: string, userId: string) => {
|
||||
cy.apiAddUserToChannel(channelId, userId);
|
||||
promoteToChannelAdmin(userId, channelId, admin);
|
||||
};
|
||||
|
||||
// Helper function to make user a team admin
|
||||
const makeUserTeamAdmin = (teamId: string, userId: string) => {
|
||||
cy.apiUpdateTeamMemberSchemeRole(teamId, userId, {scheme_admin: true, scheme_user: true});
|
||||
};
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset to a clean state before each test
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
describe('Basic Conversion Functionality', () => {
|
||||
it('MM-T3348-1 - Can convert a public channel to private', () => {
|
||||
// # Setup permissions
|
||||
setupPermissions({resetToDefault: true, publicToPrivate: true});
|
||||
|
||||
// # Create and visit a public channel
|
||||
createAndVisitPublicChannel(testTeam.name, 'public-to-private', 'Public To Private').then((channel) => {
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// # Convert to private
|
||||
convertChannelToPrivate();
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
|
||||
// * Verify channel is now private
|
||||
verifyChannelIsPrivate(channel.display_name);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3348-5 - Cannot convert a private channel back to public', () => {
|
||||
// # Setup permissions
|
||||
setupPermissions({resetToDefault: true, publicToPrivate: true});
|
||||
|
||||
// # Create and visit a public channel
|
||||
createAndVisitPublicChannel(testTeam.name, 'private-stays-private', 'Private Stays Private').then((channel) => {
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// # Convert to private
|
||||
convertChannelToPrivate();
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
|
||||
// * Verify channel is now private
|
||||
verifyChannelIsPrivate(channel.display_name);
|
||||
|
||||
// # Open channel settings modal again
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option to public is disabled
|
||||
verifyConversionOptionDisabled(false);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Role-Based Channel Type Conversion', () => {
|
||||
it('MM-T3350-1 - System admin cannot convert a private channel to public', () => {
|
||||
// # Reset permissions to default
|
||||
resetPermissionsToDefault();
|
||||
|
||||
// # Login as system admin
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Create and visit a private channel
|
||||
createAndVisitPrivateChannel(testTeam.name, 'sysadmin-private-stays-private', 'SysAdmin Private Channel').then(() => {
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option to public is disabled
|
||||
verifyConversionOptionDisabled(false);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3350-2 - Channel admin cannot convert a private channel to public', () => {
|
||||
// # Reset permissions to default
|
||||
setupPermissions({resetToDefault: true});
|
||||
|
||||
// # Login as regular user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create and visit a private channel
|
||||
createAndVisitPrivateChannel(testTeam.name, 'channel-admin-private', 'Channel Admin Private').then((channel) => {
|
||||
// # Make test user a channel admin
|
||||
makeUserChannelAdmin(channel.id, testUser.id);
|
||||
|
||||
// # Visit the channel again after role change
|
||||
visitChannel(testTeam.name, channel.name);
|
||||
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option to public is disabled
|
||||
verifyConversionOptionDisabled(false);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3350-3 - Team admin cannot convert a private channel to public', () => {
|
||||
// # Reset permissions to default
|
||||
setupPermissions({resetToDefault: true});
|
||||
|
||||
// # Make test user a team admin
|
||||
makeUserTeamAdmin(testTeam.id, testUser.id);
|
||||
|
||||
// # Login as team admin
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create and visit a private channel
|
||||
createAndVisitPrivateChannel(testTeam.name, 'team-admin-private', 'Team Admin Private').then(() => {
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option to public is disabled
|
||||
verifyConversionOptionDisabled(false);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3350-4 - Regular user cannot convert a private channel to public', () => {
|
||||
// # Reset permissions to default
|
||||
resetPermissionsToDefault();
|
||||
|
||||
// # Create a new regular user specifically for this test
|
||||
cy.apiCreateUser().then(({user: regularUser}) => {
|
||||
// # Add user to the team
|
||||
cy.apiAddUserToTeam(testTeam.id, regularUser.id);
|
||||
|
||||
// # Have admin create a private channel
|
||||
cy.apiAdminLogin();
|
||||
cy.apiCreateChannel(testTeam.id, 'admin-created-private', 'Admin Created Private', 'P').then(({channel}) => {
|
||||
// # Add the regular user to the channel (as a regular member, not admin)
|
||||
cy.apiAddUserToChannel(channel.id, regularUser.id);
|
||||
|
||||
// # Login as the regular user
|
||||
cy.apiLogin(regularUser);
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option is disabled
|
||||
verifyConversionOptionDisabled(false);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permission-Based Tests', () => {
|
||||
it('MM-T3348-2 - Regular user without permission cannot convert public to private', () => {
|
||||
// # Setup permissions - remove public to private conversion permission
|
||||
setupPermissions({resetToDefault: true, publicToPrivate: false});
|
||||
|
||||
// # Create a new regular user specifically for this test
|
||||
cy.apiCreateUser().then(({user: regularUser}) => {
|
||||
// # Add user to the team
|
||||
cy.apiAddUserToTeam(testTeam.id, regularUser.id);
|
||||
|
||||
// # Have admin create a public channel
|
||||
cy.apiAdminLogin();
|
||||
cy.apiCreateChannel(testTeam.id, 'admin-created-public', 'Admin Created Public').then(({channel}) => {
|
||||
// # Add the regular user to the channel (as a regular member, not admin)
|
||||
cy.apiAddUserToChannel(channel.id, regularUser.id);
|
||||
|
||||
// # Login as the regular user
|
||||
cy.apiLogin(regularUser);
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option is disabled
|
||||
verifyConversionOptionDisabled(true);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3348-3 - Team admin can convert public to private by default', () => {
|
||||
// # Setup permissions - reset to default
|
||||
setupPermissions({resetToDefault: true});
|
||||
|
||||
// # Make test user a team admin
|
||||
makeUserTeamAdmin(testTeam.id, testUser.id);
|
||||
|
||||
// # Create and visit a public channel
|
||||
createAndVisitPublicChannel(testTeam.name, 'team-admin-convert', 'Team Admin Convert').then((channel) => {
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option is enabled
|
||||
verifyConversionOptionEnabled(true);
|
||||
|
||||
// # Convert to private
|
||||
convertChannelToPrivate();
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
|
||||
// * Verify channel is now private
|
||||
verifyChannelIsPrivate(channel.display_name);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3348-4 - Team admin cannot convert when permission is removed', () => {
|
||||
// # Setup permissions - remove permission from team admin
|
||||
setupPermissions({
|
||||
resetToDefault: true,
|
||||
removeFromTeamAdmin: true,
|
||||
});
|
||||
|
||||
// # Make test user a team admin
|
||||
makeUserTeamAdmin(testTeam.id, testUser.id);
|
||||
|
||||
// # Have admin create a public channel
|
||||
cy.apiAdminLogin();
|
||||
cy.apiCreateChannel(testTeam.id, 'admin-created-team-admin-no-perm', 'Admin Created Team Admin No Permission').then(({channel}) => {
|
||||
// # Add the team admin user to the channel (as a regular member, not channel admin)
|
||||
cy.apiAddUserToChannel(channel.id, testUser.id);
|
||||
|
||||
// # Login as the team admin user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option is disabled
|
||||
verifyConversionOptionDisabled(true);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Channel Admin Tests', () => {
|
||||
it('MM-T3349-1 - Channel admin can convert public to private when permission is enabled', () => {
|
||||
// # Setup permissions - enable public to private conversion permission
|
||||
setupPermissions({resetToDefault: true, publicToPrivate: true});
|
||||
|
||||
// # Create and visit a public channel
|
||||
createAndVisitPublicChannel(testTeam.name, 'channel-admin-pub', 'Channel Admin Public').then((channel) => {
|
||||
// # Make test user a channel admin
|
||||
makeUserChannelAdmin(channel.id, testUser.id);
|
||||
|
||||
// # Visit the channel again after role change
|
||||
visitChannel(testTeam.name, channel.name);
|
||||
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option is enabled
|
||||
verifyConversionOptionEnabled(true);
|
||||
|
||||
// # Convert to private
|
||||
convertChannelToPrivate();
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
|
||||
// * Verify channel is now private
|
||||
verifyChannelIsPrivate(channel.display_name);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3349-2 - Channel admin cannot convert public to private when permission is removed', () => {
|
||||
// Generate a unique channel name with timestamp to avoid conflicts
|
||||
const timestamp = Date.now();
|
||||
const channelId = `channel-admin-no-perm-${timestamp}`;
|
||||
const displayName = `Channel Admin No Permission ${timestamp}`;
|
||||
|
||||
// # Setup permissions - remove public to private conversion permission
|
||||
setupPermissions({resetToDefault: true, publicToPrivate: false, removeFromTeamAdmin: true});
|
||||
|
||||
// # Create and visit a public channel
|
||||
createAndVisitPublicChannel(testTeam.name, channelId, displayName).then((channel) => {
|
||||
// # Make test user a channel admin
|
||||
makeUserChannelAdmin(channel.id, testUser.id);
|
||||
|
||||
// # Log back in as the test user after making channel admin
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Wait for login to complete
|
||||
cy.wait(500);
|
||||
|
||||
// # Visit the channel again after role change
|
||||
visitChannel(testTeam.name, channel.name);
|
||||
|
||||
// # Wait for channel to load
|
||||
cy.wait(500);
|
||||
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option is disabled
|
||||
verifyConversionOptionDisabled(true);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -58,19 +58,7 @@ describe('Leave and Archive channel actions display as destructive', () => {
|
||||
cy.get('#channelMembers').should('be.visible');
|
||||
|
||||
// * Channel Settings menu option should be visible
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||
|
||||
// * Edit Channel Header menu option should be visible
|
||||
cy.get('#channelEditHeader').should('be.visible');
|
||||
|
||||
// * Edit Channel Purpose menu option should be visible
|
||||
cy.get('#channelEditPurpose').should('be.visible');
|
||||
|
||||
// * Rename Channel menu option should be visible
|
||||
cy.get('#channelRename').should('be.visible');
|
||||
|
||||
// * Channel Settings close menu option
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseout');
|
||||
cy.findByText('Channel Settings').should('be.visible');
|
||||
|
||||
// * Archive Channel menu option should be visible and have a background-color (destructive)
|
||||
cy.get('#channelArchiveChannel').should('be.visible').focus().should('have.css', 'background-color', 'rgb(210, 75, 78)');
|
||||
|
||||
@@ -14,15 +14,13 @@ import * as TIMEOUTS from '../../../fixtures/timeouts';
|
||||
import {getRandomId} from '../../../utils';
|
||||
|
||||
describe('Channel routing', () => {
|
||||
let testTeam: Cypress.Team;
|
||||
let testUser: Cypress.UserProfile;
|
||||
let testChannel: Cypress.Channel;
|
||||
let testTeam: any;
|
||||
let testUser: any;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user, channel}) => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
testChannel = channel;
|
||||
|
||||
// # Login as test user and go to town square
|
||||
cy.apiLogin(testUser);
|
||||
@@ -34,24 +32,25 @@ describe('Channel routing', () => {
|
||||
// # Create new test channel
|
||||
cy.uiCreateChannel({name: 'Test__Channel'});
|
||||
|
||||
// # Click on channel menu and press rename channel
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
// # Open channel settings modal from channel header dropdown
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// * Channel Settings menu option should be visible
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||
cy.findByText('Rename Channel').click();
|
||||
// # Assert if the channel settings modal is present
|
||||
cy.get('.ChannelSettingsModal').should('be.visible');
|
||||
|
||||
// # Assert if the rename modal present
|
||||
cy.get('[aria-labelledby="renameChannelModalLabel"').should('be.visible').within(() => {
|
||||
// # type the two 26 character strings with 2 underscores between them and click on save
|
||||
cy.get('#channel_name').clear().type('uzsfmtmniifsjgesce4u7yznyh__uzsfmtmniifsjgesce5u7yznyh', {force: true}).wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#save-button').should('be.visible').click();
|
||||
// # Click on the URL input button to edit the URL
|
||||
cy.get('.url-input-button').should('be.visible').click({force: true});
|
||||
|
||||
// # Assert the error occurred with the appropriate message
|
||||
cy.get('.input__help').should('have.class', 'error');
|
||||
cy.get('.input__help').should('have.text', 'User IDs are not allowed in channel URLs.');
|
||||
cy.findByText('Cancel').click();
|
||||
});
|
||||
// # Type the two 26 character strings with 2 underscores between them
|
||||
cy.get('.url-input-container input').clear().type('uzsfmtmniifsjgesce4u7yznyh__uzsfmtmniifsjgesce5u7yznyh', {force: true}).wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// # Assert the error occurred with the appropriate message
|
||||
cy.get('.SaveChangesPanel').should('contain', 'There are errors in the form above');
|
||||
cy.get('.url-input-error').should('contain', 'User IDs are not allowed in channel URLs.');
|
||||
|
||||
// # Close the modal
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
});
|
||||
|
||||
it('MM-T884_2 Creating new channel validates against two user IDs being used as channel name', () => {
|
||||
@@ -97,42 +96,34 @@ describe('Channel routing', () => {
|
||||
it('MM-T883 Channel URL validation for spaces between characters', () => {
|
||||
const firstWord = getRandomId(26);
|
||||
const secondWord = getRandomId(26);
|
||||
const channelName = 'test-channel-' + getRandomId(8);
|
||||
|
||||
// # In a test channel, click the "v" to the right of the channel name in the header
|
||||
cy.findByText(`${testChannel.display_name}`).click();
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
// # Create a new test channel and navigate to it
|
||||
cy.apiCreateChannel(testTeam.id, channelName, 'Test Channel for URL Validation').then(({channel}) => {
|
||||
cy.apiAddUserToChannel(channel.id, testUser.id);
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
});
|
||||
|
||||
// * Channel Settings menu option should be visible
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||
|
||||
// # Select "Rename Channel"
|
||||
cy.findByText('Rename Channel').click();
|
||||
// # Open channel settings modal from channel header dropdown
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Change the channel name to {26 alphanumeric characters}[insert 2 spaces]{26 alphanumeric characters}
|
||||
// i.e. a total of 54 characters separated by 2 spaces
|
||||
cy.get('#display_name').clear().type(`${firstWord}${Cypress._.repeat(' ', 2)}${secondWord}`);
|
||||
cy.get('#input_channel-settings-name').clear().type(`${firstWord}${Cypress._.repeat(' ', 2)}${secondWord}`);
|
||||
|
||||
// # Hit Save
|
||||
cy.findByText('Save').click();
|
||||
// # 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();
|
||||
|
||||
// * The channel name should be updated to the characters you typed with only 1 space between the characters (extra spaces are trimmed)
|
||||
cy.get('#channelHeaderTitle').contains(`${firstWord} ${secondWord}`);
|
||||
|
||||
// # In a test channel, click the "v" to the right of the channel name in the header
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// * Channel Settings menu option should be visible
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||
|
||||
// # Select "Rename Channel"
|
||||
cy.findByText('Rename Channel').click();
|
||||
|
||||
// # Change the URL to {26 alphanumeric characters}--{26 alphanumeric characters}
|
||||
cy.get('#channel_name').clear().type(`${firstWord}${Cypress._.repeat('-', 2)}${secondWord}`);
|
||||
|
||||
// # Hit Save
|
||||
cy.findByText('Save').click();
|
||||
|
||||
// * The channel URL should be updated to the characters you typed, separated by 2 dashes
|
||||
cy.url().should('equal', `${Cypress.config('baseUrl')}/${testTeam.name}/channels/${firstWord}${Cypress._.repeat('-', 2)}${secondWord}`);
|
||||
});
|
||||
|
||||
@@ -202,22 +202,33 @@ describe('Team Permissions', () => {
|
||||
// # Visit the `Off-Topic` channel in the new team
|
||||
cy.visit(`/${team.name}/channels/off-topic`);
|
||||
|
||||
// # Open channel header menu
|
||||
cy.uiOpenChannelMenu().wait(TIMEOUTS.HALF_SEC);
|
||||
// # Open channel header dropdown
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
|
||||
// * Verify dropdown opens
|
||||
cy.get('#channelHeaderDropdownMenu').should('be.visible');
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||
// * Verify Channel Settings option is visible
|
||||
cy.findByText('Channel Settings').should('be.visible');
|
||||
|
||||
// * Verify `Edit Channel Header` menu item is visible
|
||||
cy.get('#channelEditHeader').should('be.visible');
|
||||
// # Click on Channel Settings
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// * Verify `Edit Channel Purpose` menu item is visible
|
||||
cy.get('#channelEditPurpose').should('be.visible');
|
||||
// * Verify Channel Settings modal opens
|
||||
cy.get('.ChannelSettingsModal').should('be.visible');
|
||||
|
||||
// * Verify `Rename Channel` menu item is visible
|
||||
cy.get('#channelRename').should('be.visible');
|
||||
// * Verify user can edit channel name
|
||||
cy.get('#input_channel-settings-name').should('be.visible').and('not.be.disabled');
|
||||
|
||||
// * Verify user can edit channel URL
|
||||
cy.get('.url-input-button').should('be.visible').and('not.be.disabled');
|
||||
|
||||
// * Verify user can edit channel purpose
|
||||
cy.get('#channel_settings_purpose_textbox').should('be.visible').and('not.be.disabled');
|
||||
|
||||
// * Verify user can edit channel header
|
||||
cy.get('#channel_settings_header_textbox').should('be.visible').and('not.be.disabled');
|
||||
|
||||
// # Close the modal
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
cy.get('.ChannelSettingsModal').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -114,13 +114,15 @@ describe('Keyboard shortcut CTRL/CMD+Shift+\\ for adding reaction to last messag
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
verifyEmojiPickerNotOpen();
|
||||
|
||||
['Edit Channel Header', 'Rename Channel'].forEach((modal) => {
|
||||
// # Click on the channel name in the channel header to open the channel menu options
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
// # Open Channel Settings Modal
|
||||
cy.uiOpenChannelMenu('Channel Settings');
|
||||
verifyEmojiPickerNotOpen();
|
||||
|
||||
// # Select the "Edit Channel Header" option from the dropdown
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||
cy.findByText(modal).click();
|
||||
['Channel Purpose', 'Channel Header'].forEach((modal) => {
|
||||
// # Open channel menu and click View Info
|
||||
cy.uiOpenChannelMenu('View Info');
|
||||
|
||||
cy.findByText(modal).parent().findAllByRole('button', {name: 'Edit'}).click();
|
||||
|
||||
doReactToLastMessageShortcut();
|
||||
|
||||
@@ -129,6 +131,9 @@ describe('Keyboard shortcut CTRL/CMD+Shift+\\ for adding reaction to last messag
|
||||
|
||||
// # Close the modal
|
||||
pressEscapeKey();
|
||||
|
||||
// # Close channel info
|
||||
cy.uiOpenChannelMenu('Close Info');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('Onboarding', () => {
|
||||
// # Enable any user with an account on the server to join the team
|
||||
cy.get('input.mm-modal-generic-section-item__input-checkbox').last().should('be.visible').click();
|
||||
|
||||
cy.findAllByTestId('mm-save-changes-panel__save-btn').should('be.visible').click();
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').should('be.visible').click();
|
||||
|
||||
// # Close the modal
|
||||
cy.findByLabelText('Close').should('be.visible').click();
|
||||
|
||||
@@ -102,11 +102,19 @@ describe('channels > run', {testIsolation: true}, () => {
|
||||
cy.startPlaybookRunWithSlashCommand('Playbook', playbookRunName);
|
||||
cy.verifyPlaybookRunActive(testTeam.id, playbookRunName);
|
||||
|
||||
// # Open the channel header
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||
// # 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');
|
||||
|
||||
// * Verify the ability to edit the channel header exists
|
||||
cy.get('#channelEditHeader').should('exist');
|
||||
cy.get('#channel_settings_header_textbox').should('be.visible').and('not.be.disabled');
|
||||
|
||||
// # Close the modal
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -462,20 +462,35 @@ function getCurrentChannelId(): ChainableT<string> {
|
||||
Cypress.Commands.add('getCurrentChannelId', getCurrentChannelId);
|
||||
|
||||
function updateChannelHeader(text: string) {
|
||||
cy.get('#channelHeaderTitle').
|
||||
should('be.visible').
|
||||
click();
|
||||
cy.get('#channelHeaderDropdownMenu').
|
||||
should('be.visible');
|
||||
// # Open channel header dropdown
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
|
||||
// * Channel Settings menu option should be visible
|
||||
cy.findByText('Channel Settings').should('be.visible').trigger('mouseover');
|
||||
cy.findByText('Edit Channel Header').click();
|
||||
cy.get('#edit_textbox').
|
||||
// # 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).
|
||||
type('{enter}').
|
||||
wait(TIMEOUTS.HALF_SEC);
|
||||
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);
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator, expect} from '@playwright/test';
|
||||
import {Locator, expect, Page} from '@playwright/test';
|
||||
|
||||
import ChannelsHeader from './header';
|
||||
import ChannelsPostCreate from './post_create';
|
||||
import ChannelsPostEdit from './post_edit';
|
||||
import ChannelsPost from './post';
|
||||
|
||||
import {duration} from '@/util';
|
||||
import {duration, hexToRgb} from '@/util';
|
||||
import {waitUntil} from '@/test_action';
|
||||
|
||||
export default class ChannelsCenterView {
|
||||
readonly container: Locator;
|
||||
readonly page: Page;
|
||||
|
||||
readonly header;
|
||||
readonly postCreate;
|
||||
@@ -27,9 +28,12 @@ export default class ChannelsCenterView {
|
||||
readonly scheduledDraftSeeAllLink;
|
||||
readonly postEdit;
|
||||
readonly editedPostIcon;
|
||||
readonly channelBanner;
|
||||
|
||||
constructor(container: Locator) {
|
||||
constructor(container: Locator, page: Page) {
|
||||
this.container = container;
|
||||
this.page = page;
|
||||
|
||||
this.scheduledDraftChannelInfoMessageLocator = 'span:has-text("Message scheduled for")';
|
||||
this.scheduledDraftDMChannelLocatorString = 'div.ScheduledPostIndicator span a';
|
||||
this.header = new ChannelsHeader(this.container.locator('.channel-header'));
|
||||
@@ -43,6 +47,7 @@ export default class ChannelsCenterView {
|
||||
this.scheduledDraftDMChannelLocator = container.locator(this.scheduledDraftDMChannelLocatorString);
|
||||
this.scheduledDraftSeeAllLink = container.locator('a:has-text("See all")');
|
||||
this.editedPostIcon = (postID: string) => container.locator(`#postEdited_${postID}`);
|
||||
this.channelBanner = container.getByTestId('channel_banner_container');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -156,4 +161,45 @@ export default class ChannelsCenterView {
|
||||
await this.editedPostIcon(postID).click();
|
||||
}
|
||||
}
|
||||
|
||||
async assertChannelBanner(text: string, backgroundColor: string) {
|
||||
await expect(this.channelBanner).toBeVisible();
|
||||
|
||||
const actualText = await this.channelBanner.textContent();
|
||||
expect(actualText).toBe(text);
|
||||
|
||||
const actualBackgroundColor = await this.channelBanner.evaluate((el) => {
|
||||
return window.getComputedStyle(el).getPropertyValue('background-color');
|
||||
});
|
||||
|
||||
expect(actualBackgroundColor).toBe(hexToRgb(backgroundColor));
|
||||
}
|
||||
|
||||
async assertChannelBannerNotVisible() {
|
||||
await expect(this.channelBanner).not.toBeVisible();
|
||||
}
|
||||
|
||||
async assertChannelBannerHasBoldText(text: string) {
|
||||
const boldText = await this.channelBanner.locator('strong');
|
||||
expect(boldText).toBeVisible();
|
||||
|
||||
const actualText = await boldText.textContent();
|
||||
expect(actualText).toBe(text);
|
||||
}
|
||||
|
||||
async assertChannelBannerHasItalicText(text: string) {
|
||||
const italicText = await this.channelBanner.locator('em');
|
||||
expect(italicText).toBeVisible();
|
||||
|
||||
const actualText = await italicText.textContent();
|
||||
expect(actualText).toBe(text);
|
||||
}
|
||||
|
||||
async assertChannelBannerHasStrikethroughText(text: string) {
|
||||
const strikethroughText = await this.channelBanner.locator('del');
|
||||
expect(strikethroughText).toBeVisible();
|
||||
|
||||
const actualText = await strikethroughText.textContent();
|
||||
expect(actualText).toBe(text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,20 @@ import {Locator, expect} from '@playwright/test';
|
||||
export default class ChannelsHeader {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly channelMenuDropdown;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.channelMenuDropdown = container.locator('[aria-controls="channelHeaderDropdownMenu"]');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async openChannelMenu() {
|
||||
await this.channelMenuDropdown.isVisible();
|
||||
await this.channelMenuDropdown.click();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator, expect} from '@playwright/test';
|
||||
|
||||
export default class ConfigurationSettings {
|
||||
readonly container: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async save() {
|
||||
const saveButton = this.container.getByTestId('SaveChangesPanel__save-btn');
|
||||
await expect(saveButton).toBeVisible();
|
||||
await saveButton.click();
|
||||
}
|
||||
|
||||
async enableChannelBanner() {
|
||||
const toggleButton = await this.container.getByTestId('channelBannerToggle-button');
|
||||
const classes = await toggleButton.getAttribute('class');
|
||||
if (!classes?.includes('active')) {
|
||||
await toggleButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
async disableChannelBanner() {
|
||||
const toggleButton = await this.container.getByTestId('channelBannerToggle-button');
|
||||
const classes = await toggleButton.getAttribute('class');
|
||||
if (classes?.includes('active')) {
|
||||
await toggleButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
async setChannelBannerText(text: string) {
|
||||
const textBox = await this.container.getByTestId('channel_banner_banner_text_textbox');
|
||||
await expect(textBox).toBeVisible();
|
||||
await textBox.fill(text);
|
||||
}
|
||||
|
||||
async setChannelBannerTextColor(color: string) {
|
||||
const colorInput = await this.container.locator(
|
||||
'#channel_banner_banner_background_color_picker-inputColorValue',
|
||||
);
|
||||
await expect(colorInput).toBeVisible();
|
||||
await colorInput.fill(color);
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,16 @@ import {Locator, expect} from '@playwright/test';
|
||||
import DisplaySettings from './display_settings';
|
||||
import NotificationsSettings from './notification_settings';
|
||||
|
||||
import ConfigurationSettings from '@/ui/components/channels/settings/configuration_settings';
|
||||
|
||||
export default class SettingsModal {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly notificationsSettingsTab;
|
||||
readonly configurationSettingsTab;
|
||||
|
||||
readonly notificationsSettings;
|
||||
readonly configurationSettings;
|
||||
|
||||
readonly displaySettingsTab;
|
||||
readonly displaySettings;
|
||||
@@ -19,7 +24,12 @@ export default class SettingsModal {
|
||||
this.container = container;
|
||||
|
||||
this.notificationsSettingsTab = container.locator('#notificationsButton');
|
||||
this.configurationSettingsTab = container.locator('#configurationButton');
|
||||
|
||||
this.notificationsSettings = new NotificationsSettings(container.locator('#notificationsSettings'));
|
||||
this.configurationSettings = new ConfigurationSettings(
|
||||
container.locator('.ChannelSettingsModal__configurationTab'),
|
||||
);
|
||||
|
||||
this.displaySettingsTab = container.locator('#displayButton');
|
||||
this.displaySettings = new DisplaySettings(container.locator('#displaySettings'));
|
||||
@@ -52,4 +62,19 @@ export default class SettingsModal {
|
||||
|
||||
await expect(this.container).not.toBeVisible();
|
||||
}
|
||||
|
||||
async openConfigurationTab(): Promise<ConfigurationSettings> {
|
||||
await expect(this.configurationSettingsTab).toBeVisible();
|
||||
await this.configurationSettingsTab.click();
|
||||
|
||||
await this.configurationSettings.toBeVisible();
|
||||
|
||||
return this.configurationSettings;
|
||||
}
|
||||
|
||||
async close() {
|
||||
const closeButton = this.container.locator('button.close');
|
||||
await expect(closeButton).toBeVisible();
|
||||
await closeButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import {expect, Page} from '@playwright/test';
|
||||
|
||||
import {ChannelsPost, components} from '@/ui/components';
|
||||
import SettingsModal from '@/ui/components/channels/settings/settings_modal';
|
||||
|
||||
export default class ChannelsPage {
|
||||
readonly channels = 'Channels';
|
||||
@@ -38,7 +39,7 @@ export default class ChannelsPage {
|
||||
// The main areas of the app
|
||||
this.globalHeader = new components.GlobalHeader(this, page.locator('#global-header'));
|
||||
this.searchPopover = new components.SearchPopover(page.locator('#searchPopover'));
|
||||
this.centerView = new components.ChannelsCenterView(page.getByTestId('channel_view'));
|
||||
this.centerView = new components.ChannelsCenterView(page.getByTestId('channel_view'), page);
|
||||
this.sidebarLeft = new components.ChannelsSidebarLeft(page.locator('#SidebarContainer'));
|
||||
this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right'));
|
||||
this.appBar = new components.ChannelsAppBar(page.locator('.app-bar'));
|
||||
@@ -64,6 +65,8 @@ export default class ChannelsPage {
|
||||
|
||||
// Posts
|
||||
this.postContainer = page.locator('div.post-message__text');
|
||||
|
||||
page.locator('#channelHeaderDropdownMenu');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -95,6 +98,28 @@ export default class ChannelsPage {
|
||||
await this.centerView.postMessage(message, files);
|
||||
}
|
||||
|
||||
async openChannelSettings(): Promise<SettingsModal> {
|
||||
await this.centerView.header.openChannelMenu();
|
||||
await this.page.locator('#channelSettings[role="menuitem"]').click();
|
||||
await this.settingsModal.toBeVisible();
|
||||
|
||||
return this.settingsModal;
|
||||
}
|
||||
|
||||
async newChannel(name: string, channelType: string) {
|
||||
await this.page.locator('#browseOrAddChannelMenuButton').click();
|
||||
await this.page.locator('#createNewChannelMenuItem').click();
|
||||
await this.page.locator('#input_new-channel-modal-name').fill(name);
|
||||
|
||||
if (channelType === 'P') {
|
||||
await this.page.locator('#public-private-selector-button-P').click();
|
||||
} else {
|
||||
await this.page.locator('#public-private-selector-button-O').click();
|
||||
}
|
||||
|
||||
await this.page.getByText('Create channel').click();
|
||||
}
|
||||
|
||||
async openUserAccountMenu() {
|
||||
await this.userAccountMenuButton.click();
|
||||
await expect(this.userAccountMenu.container).toBeVisible();
|
||||
|
||||
@@ -46,3 +46,16 @@ export const defaultTeam = {name: 'ad-1', displayName: 'eligendi', type: 'O'};
|
||||
|
||||
export const illegalRe = /[/?<>\\:*|":&();]/g;
|
||||
export const simpleEmailRe = /\S+@\S+\.\S+/;
|
||||
|
||||
export function hexToRgb(hex: string): string {
|
||||
// Remove the # if present
|
||||
hex = hex.replace(/^#/, '');
|
||||
|
||||
// Parse the hex values
|
||||
const r = parseInt(hex.substring(0, 2), 16);
|
||||
const g = parseInt(hex.substring(2, 4), 16);
|
||||
const b = parseInt(hex.substring(4, 6), 16);
|
||||
|
||||
// Return the RGB string
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
|
||||
2402
e2e-tests/playwright/package-lock.json
сгенерированный
2402
e2e-tests/playwright/package-lock.json
сгенерированный
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {test} from '@mattermost/playwright-lib';
|
||||
import {getRandomId} from 'utils/utils';
|
||||
|
||||
test('Should show channel banner when configured', async ({pw}) => {
|
||||
const {adminUser, adminClient} = await pw.initSetup();
|
||||
const license = await adminClient.getClientLicenseOld();
|
||||
test.skip(license.SkuShortName !== 'premium', 'Skipping test - server does not have Premium license');
|
||||
|
||||
const {channelsPage} = await pw.testBrowser.login(adminUser);
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
await channelsPage.newChannel(getRandomId(), 'O');
|
||||
|
||||
let settingsModal = await channelsPage.openChannelSettings();
|
||||
let configurationTab = await settingsModal.openConfigurationTab();
|
||||
|
||||
await configurationTab.enableChannelBanner();
|
||||
await configurationTab.setChannelBannerText('Example channel banner text');
|
||||
await configurationTab.setChannelBannerTextColor('#77DD88');
|
||||
|
||||
await configurationTab.save();
|
||||
await settingsModal.closeModal();
|
||||
|
||||
await channelsPage.centerView.assertChannelBanner('Example channel banner text', '#77DD88');
|
||||
|
||||
// Now we'll disable the channel banner
|
||||
settingsModal = await channelsPage.openChannelSettings();
|
||||
configurationTab = await settingsModal.openConfigurationTab();
|
||||
await configurationTab.disableChannelBanner();
|
||||
|
||||
await configurationTab.save();
|
||||
await settingsModal.closeModal();
|
||||
|
||||
await channelsPage.centerView.assertChannelBannerNotVisible();
|
||||
|
||||
// re-enabling channel banner should already have
|
||||
// the previously configured text and color
|
||||
settingsModal = await channelsPage.openChannelSettings();
|
||||
configurationTab = await settingsModal.openConfigurationTab();
|
||||
await configurationTab.enableChannelBanner();
|
||||
|
||||
await configurationTab.save();
|
||||
await settingsModal.closeModal();
|
||||
|
||||
await channelsPage.centerView.assertChannelBanner('Example channel banner text', '#77DD88');
|
||||
});
|
||||
|
||||
test('Should render markdown', async ({pw}) => {
|
||||
const {adminUser, adminClient} = await pw.initSetup();
|
||||
const license = await adminClient.getClientLicenseOld();
|
||||
test.skip(license.SkuShortName !== 'premium', 'Skipping test - server does not have Premium license');
|
||||
|
||||
const {channelsPage} = await pw.testBrowser.login(adminUser);
|
||||
await channelsPage.goto();
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
await channelsPage.newChannel(getRandomId(), 'O');
|
||||
|
||||
const settingsModal = await channelsPage.openChannelSettings();
|
||||
const configurationTab = await settingsModal.openConfigurationTab();
|
||||
|
||||
await configurationTab.enableChannelBanner();
|
||||
await configurationTab.setChannelBannerText('**bold** *italic* ~~strikethrough~~');
|
||||
await configurationTab.setChannelBannerTextColor('#77DD88');
|
||||
|
||||
await configurationTab.save();
|
||||
await settingsModal.closeModal();
|
||||
|
||||
await channelsPage.centerView.assertChannelBannerHasBoldText('bold');
|
||||
await channelsPage.centerView.assertChannelBannerHasItalicText('italic');
|
||||
await channelsPage.centerView.assertChannelBannerHasStrikethroughText('strikethrough');
|
||||
});
|
||||
12
e2e-tests/playwright/utils/utils.ts
Обычный файл
12
e2e-tests/playwright/utils/utils.ts
Обычный файл
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
|
||||
export function getRandomId(length = 7): string {
|
||||
const MAX_SUBSTRING_INDEX = 27;
|
||||
|
||||
return uuidv4()
|
||||
.replace(/-/g, '')
|
||||
.substring(MAX_SUBSTRING_INDEX - length, MAX_SUBSTRING_INDEX);
|
||||
}
|
||||
Ссылка в новой задаче
Block a user