snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 1s
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @enterprise @system_console @announcement_banner
|
||||
|
||||
import {hexToRgbArray, rgbArrayToString} from '@/utils';
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Announcement Banner', () => {
|
||||
before(() => {
|
||||
cy.apiRequireLicense();
|
||||
});
|
||||
|
||||
it('MM-T1128 Announcement Banner - Dismissible banner shows long text truncated', () => {
|
||||
const bannerEmbedLink = 'http://example.com';
|
||||
const bannerEndLink = 'http://example.com/the_end';
|
||||
const bannerText = `Here's an announcement! It has a link: ${bannerEmbedLink}. It's a really long announcement, because we have a lot to say. Be sure to read it all, click the link, then dismiss the banner, and then you can go on to the next test, which will have a shorter announcement. Thank you for reading [to the end](${bannerEndLink}) and have a nice day!`;
|
||||
const bannerBgColor = '#4378da';
|
||||
const bannerBgColorRGBArray = hexToRgbArray(bannerBgColor);
|
||||
const bannerTextColor = '#ffffff';
|
||||
const bannerTextColorRGBArray = hexToRgbArray(bannerTextColor);
|
||||
|
||||
// # Go to announcement banner config page of system console
|
||||
cy.visit('/admin_console/site_config/announcement_banner');
|
||||
|
||||
// # Enable banner if not already enabled
|
||||
cy.findByTestId('AnnouncementSettings.EnableBanner').
|
||||
should('be.visible').
|
||||
within(() => {
|
||||
cy.findByTestId('AnnouncementSettings.EnableBannertrue').
|
||||
should('be.visible').
|
||||
click({force: true});
|
||||
});
|
||||
|
||||
// # Enter the long banner text
|
||||
cy.findByTestId('AnnouncementSettings.BannerText').
|
||||
should('be.visible').
|
||||
within(() => {
|
||||
cy.get('input').
|
||||
should('be.visible').
|
||||
clear().
|
||||
invoke('val', bannerText).
|
||||
wait(TIMEOUTS.HALF_SEC).
|
||||
type(' {backspace}{enter}');
|
||||
});
|
||||
|
||||
// # Change the banner background color
|
||||
cy.findByTestId('AnnouncementSettings.BannerColor').
|
||||
should('be.visible').
|
||||
within(() => {
|
||||
cy.get('input').
|
||||
should('be.visible').
|
||||
clear().
|
||||
type(bannerBgColor);
|
||||
});
|
||||
|
||||
// # Change the banner text color
|
||||
cy.findByTestId('AnnouncementSettings.BannerTextColor').
|
||||
should('be.visible').
|
||||
within(() => {
|
||||
cy.get('input').
|
||||
should('be.visible').
|
||||
clear().
|
||||
type(bannerTextColor);
|
||||
});
|
||||
|
||||
// # Allow for banner dismissal to true
|
||||
cy.findByTestId('AnnouncementSettings.AllowBannerDismissal').
|
||||
should('be.visible').
|
||||
within(() => {
|
||||
cy.findByTestId('AnnouncementSettings.AllowBannerDismissaltrue').
|
||||
should('be.visible').
|
||||
click({force: true});
|
||||
});
|
||||
|
||||
// # Click on the save button
|
||||
cy.get('.admin-console').
|
||||
should('exist').
|
||||
within(() => {
|
||||
cy.findByText('Save').should('be.visible').click();
|
||||
});
|
||||
|
||||
// * Verify banner overflow is hidden from viewport
|
||||
// also verify its background color and text color matches to configuration entered
|
||||
// and check if the url in the text rendered as anchor tag
|
||||
cy.get('.announcement-bar').
|
||||
as('announcementBanner').
|
||||
should('exist').
|
||||
and('is.visible').
|
||||
and('have.css', 'overflow', 'hidden').
|
||||
and(
|
||||
'have.css',
|
||||
'background-color',
|
||||
rgbArrayToString(bannerBgColorRGBArray),
|
||||
).
|
||||
and('have.css', 'color', rgbArrayToString(bannerTextColorRGBArray)).
|
||||
contains('a', bannerEmbedLink).
|
||||
should('have.attr', 'href', bannerEmbedLink);
|
||||
|
||||
// * Verify only the banner text's first part is visible by ensuring the first link
|
||||
// is visible but the second link is not
|
||||
cy.findByText(/Here's an announcement! It has a link: /).
|
||||
should('be.visible').
|
||||
within(() => {
|
||||
cy.get(`a[href="${bannerEmbedLink}"]`).should('be.visible');
|
||||
cy.get(`a[href="${bannerEndLink}"]`).should('not.be.visible');
|
||||
});
|
||||
|
||||
// Go back to Channels
|
||||
cy.get('a.backstage-navbar__back').click();
|
||||
|
||||
// # Hover over the banner
|
||||
cy.get('@announcementBanner').trigger('mouseover');
|
||||
|
||||
// * Verify popover is visible
|
||||
cy.get('#announcement-bar__tooltip').
|
||||
as('announcementBannerTooltip').
|
||||
should('be.visible').
|
||||
within(() => {
|
||||
// * Verify complete banner is present in the popover
|
||||
cy.findByText(/Here's an announcement! It has a link: /).should(
|
||||
'be.visible',
|
||||
);
|
||||
cy.findByText(
|
||||
/. It's a really long announcement, because we have a lot to say. Be sure to read it all, click the link, then dismiss the banner, and then you can go on to the next test, which will have a shorter announcement. Thank you for reading and have a nice day!/,
|
||||
).should('be.visible');
|
||||
});
|
||||
|
||||
// # Move mouse out of banner area
|
||||
cy.get('@announcementBanner').trigger('mouseout');
|
||||
|
||||
// * Verify the popover is no more visible
|
||||
cy.get('@announcementBannerTooltip').should('not.exist');
|
||||
|
||||
// # Close the banner
|
||||
cy.get('.announcement-bar__close').should('be.visible').click();
|
||||
|
||||
// * Verify the banner is closed
|
||||
cy.get('@announcementBanner').should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,366 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @system_console
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Customization', () => {
|
||||
beforeEach(() => {
|
||||
// # as many of the tests logout the user, ensure it's logged
|
||||
// in as an admin before each test
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Visit customization system console page
|
||||
cy.visit('/admin_console/site_config/customization');
|
||||
cy.get('.admin-console__header').should('be.visible').and('have.text', 'Customization');
|
||||
});
|
||||
|
||||
it('MM-T1024 - Can change name and desc with Custom Branding set to false', () => {
|
||||
// # Make sure necessary field is false
|
||||
cy.apiUpdateConfig({TeamSettings: {EnableCustomBrand: false}});
|
||||
|
||||
// * Verify that setting is visible and matches text content
|
||||
cy.findByTestId('TeamSettings.SiteNamelabel').scrollIntoView().should('be.visible').and('have.text', 'Site Name:');
|
||||
|
||||
// # Update both Site Name and Description to store test values
|
||||
const siteName = 'Mattermost_Text';
|
||||
const siteDescription = 'This is a testing Mattermost site';
|
||||
cy.findByTestId('TeamSettings.SiteNameinput').clear().type(siteName);
|
||||
cy.findByTestId('TeamSettings.CustomDescriptionTextinput').clear().type(siteDescription);
|
||||
|
||||
// # Save setting
|
||||
saveSetting();
|
||||
|
||||
// # Logout
|
||||
cy.apiLogout();
|
||||
|
||||
// * Ensure that the user was redirected to the login page after the logout
|
||||
cy.url().should('include', '/login');
|
||||
|
||||
// * Ensure Site Name and Description are shown the updated values in the login screen
|
||||
cy.get('div.header-main a').should('be.visible').should('have.text', siteName);
|
||||
cy.get('p.login-body-card-title').should('have.text', siteDescription);
|
||||
});
|
||||
|
||||
it('MM-T1025 - Site Name - Product Menu ➜ About and About Modal show custom name', () => {
|
||||
// * Verify that setting is visible and matches text content
|
||||
cy.findByTestId('TeamSettings.SiteNamelabel').scrollIntoView().should('be.visible').and('have.text', 'Site Name:');
|
||||
|
||||
// # Update Site Name test value
|
||||
const siteName = "A team's instance";
|
||||
cy.findByTestId('TeamSettings.SiteNameinput').clear().type(siteName);
|
||||
|
||||
// # Save setting
|
||||
saveSetting();
|
||||
|
||||
// # Exit settings
|
||||
cy.visit('/');
|
||||
|
||||
// # Open About Mattermost menu option
|
||||
cy.uiOpenProductMenu(`About ${siteName}`);
|
||||
|
||||
// * Verify in the about modal that the new site name is being shown
|
||||
cy.get('#aboutModalLabel').should('be.visible').and('have.text', `About ${siteName}`);
|
||||
});
|
||||
|
||||
it('MM-T1026 - Custom Branding - Name character limit', () => {
|
||||
// * Verify that setting is visible and matches text content
|
||||
cy.findByTestId('TeamSettings.SiteNamelabel').scrollIntoView().should('be.visible').and('have.text', 'Site Name:');
|
||||
|
||||
// Character limit is 30, and Mattermost is exactly 10 characters long
|
||||
const siteName = 'Mattermost'.repeat(3);
|
||||
|
||||
// # Type the maximum amount of characters and then some more
|
||||
cy.findByTestId('TeamSettings.SiteNameinput').clear().type(siteName + 'something else');
|
||||
|
||||
// * Verify that the input field didn't accept more characters than the limit
|
||||
cy.findByTestId('TeamSettings.SiteNameinput').should('have.value', siteName);
|
||||
|
||||
// # Save setting
|
||||
saveSetting();
|
||||
|
||||
// * Verify that the value was saved correctly, without the extra characters
|
||||
cy.apiGetConfig().then(({config}) => {
|
||||
expect(config.TeamSettings.SiteName).to.equal(siteName);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1027 - Custom branding is enabled but no image has been uploaded', () => {
|
||||
// # Ensure that the brand image is deleted
|
||||
cy.apiDeleteBrandImage();
|
||||
cy.reload();
|
||||
|
||||
// # Set Enable Custom Branding to true
|
||||
cy.findByTestId('TeamSettings.EnableCustomBrandtrue').check();
|
||||
|
||||
// # Save setting
|
||||
saveSetting();
|
||||
|
||||
// # Logout from the current user
|
||||
cy.apiLogout();
|
||||
|
||||
// * Ensure that the user was redirected to the login page after the logout
|
||||
cy.url().should('include', '/login');
|
||||
|
||||
// * Ensure that the signup is loaded and the img doesn't exist
|
||||
cy.get('.login-body-custom-branding-image').should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T1028 - Custom brand image and text - true, and uploaded / updated', () => {
|
||||
// # Make sure necessary field is false
|
||||
cy.apiUpdateConfig({TeamSettings: {EnableCustomBrand: false}});
|
||||
|
||||
// # Ensure that the brand image is deleted
|
||||
cy.apiDeleteBrandImage();
|
||||
cy.reload();
|
||||
|
||||
// # Enable custom branding
|
||||
cy.findByTestId('TeamSettings.EnableCustomBrandtrue').check();
|
||||
|
||||
// # Upload the image
|
||||
cy.findByTestId('CustomBrandImage').should('be.visible').within(() => {
|
||||
cy.get('input').attachFile('mattermost-icon.png');
|
||||
});
|
||||
|
||||
// * Verify that custom brand image setting is visible and matches text content
|
||||
cy.findByTestId('TeamSettings.CustomBrandTextlabel').scrollIntoView().should('be.visible').and('have.text', 'Custom Brand Text:');
|
||||
|
||||
// # Update custom brand text
|
||||
const customBrandText = 'This is a custom brand text';
|
||||
cy.findByTestId('TeamSettings.CustomBrandTextinput').clear().type(customBrandText);
|
||||
|
||||
// # Save setting
|
||||
saveSetting();
|
||||
|
||||
// # Logout from the current user
|
||||
cy.apiLogout();
|
||||
|
||||
// * Ensure that the user was redirected to the login page after the logout
|
||||
cy.url().should('include', '/login');
|
||||
|
||||
// * Verify that the image is visible
|
||||
cy.get('.login-body-custom-branding-image').should('exist').and('be.visible');
|
||||
|
||||
cy.get('.login-body-custom-branding-markdown').first().within(() => {
|
||||
// * Ensure that the custom brand text has been updated
|
||||
cy.get('p').should('have.text', customBrandText);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1029 - Custom branding text can render markdown text', () => {
|
||||
const emojiText = getRandomId(3);
|
||||
|
||||
// # Visit emoji system console page
|
||||
cy.visit('/admin_console/site_config/emoji');
|
||||
cy.get('.admin-console__header').should('be.visible').and('have.text', 'Emoji');
|
||||
|
||||
// # Set Enable Custom Emoji to true
|
||||
cy.findByTestId('ServiceSettings.EnableCustomEmojitrue').check();
|
||||
|
||||
// # Save setting
|
||||
saveSetting();
|
||||
|
||||
// * Visit the add custom emoji page for ad-1
|
||||
cy.visit('/ad-1/emoji/add');
|
||||
cy.get('.backstage-header').should('be.visible');
|
||||
|
||||
// * Ensure the form is visible
|
||||
cy.get('.backstage-form').should('be.visible').within(() => {
|
||||
// # Fill the emoji data
|
||||
cy.get('input#name').clear().type(emojiText);
|
||||
cy.get('input#select-emoji').attachFile('mattermost-icon.png');
|
||||
|
||||
// # Save emoji
|
||||
cy.get('.backstage-form__footer button').click();
|
||||
});
|
||||
|
||||
// # Make sure necessary field is true
|
||||
cy.apiUpdateConfig({TeamSettings: {EnableCustomBrand: true}});
|
||||
cy.reload();
|
||||
|
||||
// # Visit customization system console page
|
||||
cy.visit('/admin_console/site_config/customization');
|
||||
cy.get('.admin-console__header').should('be.visible').and('have.text', 'Customization');
|
||||
|
||||
// eslint-disable-next-line no-irregular-whitespace
|
||||
const customBrandText = `:${emojiText}:
|
||||
:smile:
|
||||
|
||||
- This is a bullet point
|
||||
- This is another bullet point
|
||||
|
||||
**This has been bolded**
|
||||
~~This has been strikethrough~~
|
||||
*This has been italicized*
|
||||
[This is a link to mattermost.com](https://mattermost.com)
|
||||
\`This is inline code\``;
|
||||
|
||||
// # Update custom brand text
|
||||
cy.findByTestId('TeamSettings.CustomBrandTextinput').clear().type(customBrandText);
|
||||
|
||||
// # Save setting
|
||||
saveSetting();
|
||||
|
||||
// # Logout
|
||||
cy.apiLogout();
|
||||
|
||||
// * Ensure that the user was redirected to the login page after the logout
|
||||
cy.url().should('include', '/login');
|
||||
|
||||
cy.get('.login-body-custom-branding-markdown').first().scrollIntoView().should('be.visible').within(() => {
|
||||
// * Ensure default emoji has been rendered
|
||||
cy.get('span.emoticon').should('have.attr', 'alt', ':smile:');
|
||||
|
||||
// * Ensure the list and two bullets have been rendered
|
||||
cy.get('ul.markdown__list').should('be.visible').within(() => {
|
||||
cy.contains('This is a bullet point');
|
||||
cy.contains('This is another bullet point');
|
||||
});
|
||||
|
||||
// * Find within the paragraph after the list
|
||||
cy.get('ul+p').should('be.visible').within(() => {
|
||||
// * Ensure the bold text has been rendered
|
||||
cy.get('strong').should('be.visible').and('have.text', 'This has been bolded');
|
||||
|
||||
// * Ensure the strikethrough text has been rendered
|
||||
cy.get('del').should('be.visible').and('have.text', 'This has been strikethrough');
|
||||
|
||||
// * Ensure the italic text has been rendered
|
||||
cy.get('em').should('be.visible').and('have.text', 'This has been italicized');
|
||||
|
||||
// * Ensure the link has been rendered
|
||||
cy.get('a.markdown__link').should('be.visible').and('have.text', 'This is a link to mattermost.com').and('have.attr', 'href', 'https://mattermost.com');
|
||||
|
||||
// * Ensure the inline code has been rendered
|
||||
cy.get('.codespan__pre-wrap').should('be.visible').and('have.text', 'This is inline code');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1030 - Custom branding removed - Default site name and description, and brand image and text - false', () => {
|
||||
// # Make sure necessary field is true
|
||||
cy.apiUpdateConfig({TeamSettings: {EnableCustomBrand: true}});
|
||||
cy.reload();
|
||||
|
||||
// * Verify that setting is visible and matches text content
|
||||
cy.findByTestId('TeamSettings.SiteNamelabel').scrollIntoView().should('be.visible').and('have.text', 'Site Name:');
|
||||
|
||||
// # Update both Site Name and Description to store default or empty values
|
||||
const siteName = 'Mattermost';
|
||||
cy.findByTestId('TeamSettings.SiteNameinput').clear().type(siteName);
|
||||
cy.findByTestId('TeamSettings.CustomDescriptionTextinput').clear();
|
||||
|
||||
// # Remove the brand image
|
||||
cy.get('button.remove-image__btn').should('be.visible').click();
|
||||
|
||||
// # Set Enable Custom Branding to false
|
||||
cy.findByTestId('TeamSettings.EnableCustomBrandfalse').check();
|
||||
|
||||
// # Save setting
|
||||
saveSetting();
|
||||
|
||||
// # Logout
|
||||
cy.apiLogout();
|
||||
|
||||
// * Ensure that the user was redirected to the login page after the logout
|
||||
cy.url().should('include', '/login');
|
||||
|
||||
// * Ensure that the default Site Name is shown in the login screen
|
||||
cy.get('.header-logo-link svg').should('be.visible');
|
||||
|
||||
// * Ensure Description have the default values
|
||||
cy.findByText('Collaborate with your team in real-time').should('exist').and('be.visible');
|
||||
|
||||
// * Ensure that the custom branding img is not visible
|
||||
cy.get('.login-body-custom-branding-image').should('not.exist');
|
||||
|
||||
// * Ensure that the custom branding text is not visible
|
||||
cy.get('.login-body-custom-branding-markdown').should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T1282 - Site Name help text matches text field behavior', () => {
|
||||
// * Verify that the existing Site Name is empty
|
||||
cy.findByTestId('TeamSettings.SiteNameinput').should('be.empty');
|
||||
|
||||
// # Configure a custom Site Name
|
||||
const siteName = 'MM-T1282';
|
||||
cy.findByTestId('TeamSettings.SiteNameinput').clear().type(siteName);
|
||||
|
||||
// # Save setting
|
||||
saveSetting();
|
||||
|
||||
// # Logout
|
||||
cy.apiLogout();
|
||||
|
||||
// * Ensure that the user was redirected to the login page after the logout
|
||||
cy.url().should('include', '/login');
|
||||
|
||||
// * Ensure that the custom Site Name is shown
|
||||
cy.get('div.header-main a').should('be.visible').should('have.text', siteName);
|
||||
|
||||
// # Log back in as an administrator
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Visit customization system console page
|
||||
cy.visit('/admin_console/site_config/customization');
|
||||
|
||||
// * Ensure that the 'about' link and modal render the custom Site Name
|
||||
verifySiteNameInAboutModal(siteName);
|
||||
|
||||
// # Empty the Site Name configuration
|
||||
cy.findByTestId('TeamSettings.SiteNameinput').clear();
|
||||
|
||||
// # Save setting
|
||||
saveSetting();
|
||||
|
||||
// # Logout
|
||||
cy.apiLogout();
|
||||
|
||||
// * Ensure that the user was redirected to the login page after the logout
|
||||
cy.url().should('include', '/login');
|
||||
|
||||
// * Ensure that the default Site Name is shown in the login screen
|
||||
cy.get('.header-logo-link svg').should('be.visible');
|
||||
|
||||
// # Log back in as an administrator
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Visit customization system console page
|
||||
cy.visit('/admin_console/site_config/customization');
|
||||
|
||||
// * Ensure that the 'about' link and modal render the default Site Name
|
||||
verifySiteNameInAboutModal('Mattermost');
|
||||
});
|
||||
});
|
||||
|
||||
function saveSetting() {
|
||||
// # Click save button, and verify text and visibility
|
||||
cy.get('#saveSetting').
|
||||
should('have.text', 'Save').
|
||||
and('be.enabled').
|
||||
click().
|
||||
should('be.disabled').
|
||||
wait(TIMEOUTS.HALF_SEC);
|
||||
}
|
||||
|
||||
function verifySiteNameInAboutModal(siteName) {
|
||||
// # Open the hamburger menu
|
||||
cy.get('button > span[class="menu-icon"]').click();
|
||||
|
||||
// # Click to open about modal
|
||||
cy.findByText(`About ${siteName}`).click();
|
||||
|
||||
// * Verify about text is visible
|
||||
cy.findByText(`About ${siteName}`).should('be.visible');
|
||||
|
||||
// # Close the modal
|
||||
cy.get('div.modal-header button.close').should('exist').click();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
export function saveSetting() {
|
||||
// # Click save button, and verify text and visibility
|
||||
cy.get('#saveSetting').
|
||||
should('have.text', 'Save').
|
||||
and('be.enabled').
|
||||
click().
|
||||
should('be.disabled').
|
||||
wait(TIMEOUTS.HALF_SEC);
|
||||
}
|
||||
|
||||
export function backToTeam() {
|
||||
cy.get('.backstage-navbar__back').click();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @system_console @enterprise @cloud_only
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {SupportSettings} from '@/utils/constants';
|
||||
|
||||
describe('SupportSettings', () => {
|
||||
before(() => {
|
||||
// * Check if server has license for Cloud
|
||||
cy.apiRequireLicenseForFeature('Cloud');
|
||||
|
||||
// # As admin, visit default team/channel
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('MM-T1031 - Customization Change all links', () => {
|
||||
// # Open about modal
|
||||
cy.uiOpenHelpMenu().within(() => {
|
||||
// * Verify links changed
|
||||
[
|
||||
{text: 'Ask the community', link: SupportSettings.ASK_COMMUNITY_LINK},
|
||||
{text: 'Mattermost user guide', link: SupportSettings.MATTERMOST_USER_GUIDE},
|
||||
{text: 'Report a problem', link: 'mailto:reportaproblem@mattermost.com'},
|
||||
{text: 'Keyboard shortcuts'},
|
||||
].forEach(({text, link}) => {
|
||||
if (link) {
|
||||
cy.findByText(text).
|
||||
parent().
|
||||
should('have.attr', 'href').
|
||||
and('contain', link);
|
||||
} else {
|
||||
cy.findByText(text);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// # Logout
|
||||
cy.uiLogout();
|
||||
|
||||
// * Verify that the user was redirected to the login page after the logout
|
||||
cy.url().should('include', '/login');
|
||||
|
||||
const guides = [
|
||||
{text: 'About', link: SupportSettings.ABOUT_LINK},
|
||||
{text: 'Privacy Policy', link: SupportSettings.PRIVACY_POLICY_LINK},
|
||||
{text: 'Terms', link: SupportSettings.TERMS_OF_SERVICE_LINK},
|
||||
{text: 'Help', link: SupportSettings.HELP_LINK},
|
||||
];
|
||||
|
||||
// * Verify that links are correct at login page
|
||||
guides.forEach((guide) => {
|
||||
cy.findByText(guide.text).
|
||||
should('have.attr', 'href').
|
||||
and('contain', guide.link);
|
||||
});
|
||||
|
||||
// # Visit signup page
|
||||
cy.findByText('Don\'t have an account?', {timeout: TIMEOUTS.HALF_MIN}).should('be.visible').click();
|
||||
cy.url().should('include', '/signup_user_complete');
|
||||
|
||||
// * Verify that links are correct at signup page
|
||||
guides.forEach((guide) => {
|
||||
cy.findByText(guide.text).
|
||||
should('have.attr', 'href').
|
||||
and('contain', guide.link);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,212 @@
|
||||
// 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 @system_console @enterprise @e20_only @not_cloud
|
||||
|
||||
import {backToTeam, saveSetting} from './helper';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {TERMS_OF_SERVICE_LINK} from '@/utils/constants';
|
||||
|
||||
describe('SupportSettings', () => {
|
||||
const tosLink = 'https://github.com/mattermost/platform/blob/master/README.md';
|
||||
const privacyLink = 'https://github.com/mattermost/platform/blob/master/README.md';
|
||||
const aboutLink = 'https://mattermost.com/default-about/';
|
||||
const helpLink = 'https://github.com/mattermost/platform/blob/master/doc/help/README.md';
|
||||
const problemLink = 'https://forum.mattermost.com/c/trouble-shoot';
|
||||
const askCommunityLink = 'https://mattermost.com/pl/default-ask-mattermost-community';
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as admin and reset config
|
||||
cy.apiAdminLogin();
|
||||
cy.apiUpdateConfig();
|
||||
|
||||
// # Visit customization system console page
|
||||
cy.visit('/admin_console/site_config/customization');
|
||||
});
|
||||
|
||||
it('MM-T1031 - Customization Change all links', () => {
|
||||
// # Edit links in the TOS, Privacy, About, Help, Report fields
|
||||
cy.findByTestId('SupportSettings.TermsOfServiceLinkinput').clear().type(tosLink);
|
||||
cy.findByTestId('SupportSettings.PrivacyPolicyLinkinput').clear().type(privacyLink);
|
||||
cy.findByTestId('SupportSettings.AboutLinkinput').clear().type(aboutLink);
|
||||
cy.findByTestId('SupportSettings.HelpLinkinput').clear().type(helpLink);
|
||||
cy.findByTestId('SupportSettings.ReportAProblemTypedropdown').select('Custom link');
|
||||
cy.findByTestId('SupportSettings.ReportAProblemLinkinput').clear().type(problemLink);
|
||||
|
||||
// # Save setting then back to team view
|
||||
saveSetting();
|
||||
backToTeam();
|
||||
|
||||
// # Open about modal
|
||||
cy.uiOpenHelpMenu().within(() => {
|
||||
// * Verify links changed
|
||||
[
|
||||
{text: 'Report a problem', link: problemLink},
|
||||
{text: 'Ask the community', link: askCommunityLink},
|
||||
].forEach((guide) => {
|
||||
cy.findByText(guide.text).
|
||||
parent().
|
||||
should('have.attr', 'href').and('match', new RegExp(`${guide.link}/*`));
|
||||
});
|
||||
});
|
||||
|
||||
// # Logout
|
||||
cy.uiLogout();
|
||||
|
||||
// * Verify that the user was redirected to the login page after the logout
|
||||
cy.url().should('include', '/login');
|
||||
|
||||
const guides = [
|
||||
{text: 'About', link: aboutLink},
|
||||
{text: 'Privacy Policy', link: privacyLink},
|
||||
{text: 'Terms', link: tosLink},
|
||||
{text: 'Help', link: helpLink},
|
||||
];
|
||||
|
||||
// * Verify that links are correct at login page
|
||||
guides.forEach((guide) => {
|
||||
cy.findByText(guide.text).
|
||||
should('have.attr', 'href').and('match', new RegExp(`${guide.link}/*`));
|
||||
});
|
||||
|
||||
// # Visit signup page
|
||||
cy.findByText('Don\'t have an account?', {timeout: TIMEOUTS.HALF_MIN}).should('be.visible').click();
|
||||
cy.url().should('include', '/signup_user_complete');
|
||||
|
||||
// * Verify that links are correct at signup page
|
||||
cy.get('.hfroute-footer').scrollIntoView().should('be.visible').within(() => {
|
||||
guides.forEach((guide) => {
|
||||
cy.findByText(guide.text).
|
||||
should('have.attr', 'href').and('match', new RegExp(`${guide.link}/*`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1033 - Customization: Blank TOS link field (login page)', () => {
|
||||
// # Empty the "terms of services" field
|
||||
cy.findByTestId('SupportSettings.TermsOfServiceLinkinput').type('any').clear();
|
||||
|
||||
// # Save setting
|
||||
saveSetting();
|
||||
|
||||
// # Logout
|
||||
cy.apiLogout();
|
||||
|
||||
// * Verify that the user was redirected to the login page after the logout
|
||||
cy.url().should('include', '/login');
|
||||
|
||||
// * Verify that terms of services link is set to default
|
||||
cy.findByText('Terms').
|
||||
should('have.attr', 'href').and('match', new RegExp(`${TERMS_OF_SERVICE_LINK}/*`));
|
||||
});
|
||||
|
||||
it('MM-T1036 - Customization: Blank Help and Report a Problem hides options from help menu', () => {
|
||||
// # Change help and report links to blanks
|
||||
cy.findByTestId('SupportSettings.HelpLinkinput').type('any').clear();
|
||||
cy.findByTestId('SupportSettings.ReportAProblemTypedropdown').select('Hide link');
|
||||
|
||||
// # Save setting and back to team view
|
||||
saveSetting();
|
||||
backToTeam();
|
||||
|
||||
// * Verify that report link does not exist
|
||||
cy.uiOpenHelpMenu().within(() => {
|
||||
cy.get('Report a problem').should('not.exist');
|
||||
cy.get('Help resources').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1038 - Customization App download link - Change to different', () => {
|
||||
// # Edit links in the support email field
|
||||
const link = 'https://github.com/mattermost/desktop/releases';
|
||||
cy.findByTestId('NativeAppSettings.AppDownloadLinkinput').clear().type(link);
|
||||
|
||||
// # Save setting then back to team view
|
||||
saveSetting();
|
||||
backToTeam();
|
||||
|
||||
// # Open about modal
|
||||
cy.uiOpenProductMenu().within(() => {
|
||||
// * Verify that 'Download Apps' has expected link
|
||||
cy.findByText('Download Apps').
|
||||
parents('a').
|
||||
should('have.attr', 'href', link);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3289_1 - Help (Ask community link setting)', () => {
|
||||
// * Verify enable ask community link to be true by default
|
||||
cy.findByTestId('SupportSettings.EnableAskCommunityLinktrue').should('be.checked');
|
||||
|
||||
// * Verify the help text
|
||||
cy.findByTestId('SupportSettings.EnableAskCommunityLinkhelp-text').should('contain', 'When true, "Ask the community" link appears on the Mattermost user interface and Help Menu, which allows users to join the Mattermost Community to ask questions and help others troubleshoot issues. When false, the link is hidden from users.');
|
||||
|
||||
// # Back to team view
|
||||
backToTeam();
|
||||
|
||||
// * Verify that hover shows "Help" text
|
||||
cy.uiGetHelpButton().
|
||||
trigger('mouseenter').
|
||||
should('have.attr', 'aria-describedby');
|
||||
cy.uiGetHelpButton().
|
||||
trigger('mouseleave').
|
||||
should('not.have.attr', 'aria-describedby');
|
||||
|
||||
// # Open help menu
|
||||
cy.uiOpenHelpMenu().within(() => {
|
||||
// * Verify 4 options shown
|
||||
cy.findByText('Ask the community');
|
||||
cy.findByText('Report a problem');
|
||||
cy.findByText('Keyboard shortcuts');
|
||||
|
||||
// * Verify default link of Ask the community
|
||||
cy.findByText('Ask the community').
|
||||
parent().
|
||||
should('have.attr', 'href').and('match', new RegExp(`${askCommunityLink}/*`));
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3289_2 - Help (Ask community link setting)', () => {
|
||||
// Disable setting for ask community
|
||||
cy.findByTestId('SupportSettings.EnableAskCommunityLinkfalse').click();
|
||||
|
||||
// Edit help link and report a problem link
|
||||
cy.findByTestId('SupportSettings.HelpLinkinput').clear().type(helpLink);
|
||||
cy.findByTestId('SupportSettings.ReportAProblemTypedropdown').select('Custom link');
|
||||
cy.findByTestId('SupportSettings.ReportAProblemLinkinput').clear().type(problemLink);
|
||||
|
||||
// # Save setting and back to team view
|
||||
saveSetting();
|
||||
backToTeam();
|
||||
|
||||
// # Open help menu
|
||||
cy.uiOpenHelpMenu().within(() => {
|
||||
// * Verify 2 options shown
|
||||
cy.findByText('Report a problem');
|
||||
cy.findByText('Keyboard shortcuts');
|
||||
|
||||
// * Verify report a problem link has changed
|
||||
cy.findByText('Report a problem').
|
||||
parent().
|
||||
should('have.attr', 'href').and('match', new RegExp(`${problemLink}/*`));
|
||||
|
||||
// # Click on keyboard shortcuts
|
||||
cy.findByText('Keyboard shortcuts').click();
|
||||
});
|
||||
|
||||
// * Verify link opens keyboard shortcuts modal
|
||||
cy.findAllByRole('dialog', 'Keyboard shortcuts');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
// 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 @not_cloud @system_console
|
||||
|
||||
import {backToTeam, saveSetting} from './helper';
|
||||
|
||||
import {
|
||||
FixedPublicLinks,
|
||||
} from '@/utils';
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('SupportSettings', () => {
|
||||
const tosLink = 'https://github.com/mattermost/platform/blob/master/README.md';
|
||||
const privacyLink = 'https://github.com/mattermost/platform/blob/master/README.md';
|
||||
|
||||
let siteName;
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
|
||||
cy.apiGetConfig().then(({config}) => {
|
||||
siteName = config.TeamSettings.SiteName;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
cy.apiUpdateConfig();
|
||||
|
||||
// # Visit customization system console page
|
||||
cy.visit('/admin_console/site_config/customization');
|
||||
});
|
||||
|
||||
it('MM-T1032 - Customization: Custom Terms and Privacy links in the About modal', () => {
|
||||
// # Edit links in the TOS and Privacy fields
|
||||
cy.findByTestId('SupportSettings.TermsOfServiceLinkinput').clear().type(tosLink);
|
||||
cy.findByTestId('SupportSettings.PrivacyPolicyLinkinput').clear().type(privacyLink);
|
||||
|
||||
// # Save setting then back to team view
|
||||
saveSetting();
|
||||
backToTeam();
|
||||
|
||||
// # Open about modal
|
||||
cy.uiOpenProductMenu(`About ${siteName}`);
|
||||
|
||||
// * Verify that links do not change and they open to default pages
|
||||
cy.get('#tosLink').should('contain', 'Terms of Use').and('have.attr', 'href').and('match', new RegExp(`${FixedPublicLinks.TermsOfService}/*`));
|
||||
cy.get('#privacyLink').should('contain', 'Privacy Policy').and('have.attr', 'href').and('match', new RegExp(`${FixedPublicLinks.PrivacyPolicy}/*`));
|
||||
});
|
||||
|
||||
it('MM-T1034 - Customization: Blank TOS link field (About modal)', () => {
|
||||
// # Empty the "terms of services" field
|
||||
cy.findByTestId('SupportSettings.TermsOfServiceLinkinput').type('any').clear();
|
||||
|
||||
// # Save setting then back to team view
|
||||
saveSetting();
|
||||
backToTeam();
|
||||
|
||||
// # Open about modal
|
||||
cy.uiOpenProductMenu(`About ${siteName}`);
|
||||
|
||||
// * Verify that tos link is set to default
|
||||
cy.get('#tosLink').should('contain', 'Terms of Use').and('have.attr', 'href').and('match', new RegExp(`${FixedPublicLinks.TermsOfService}/*`));
|
||||
});
|
||||
|
||||
it('MM-T1035 - Customization Blank Privacy hides the link', () => {
|
||||
cy.findByTestId('SupportSettings.PrivacyPolicyLinkinput').clear();
|
||||
|
||||
// # Save setting then back to team view
|
||||
saveSetting();
|
||||
backToTeam();
|
||||
|
||||
// # Open about modal
|
||||
cy.uiOpenProductMenu(`About ${siteName}`);
|
||||
|
||||
// * Verify that tos link is there
|
||||
cy.get('#tosLink').should('be.visible').and('contain', 'Terms of Use');
|
||||
|
||||
// * Verify that privacy link is there
|
||||
cy.get('#privacyLink').should('contain', 'Privacy Policy').and('have.attr', 'href').and('match', new RegExp(`${FixedPublicLinks.PrivacyPolicy}/*`));
|
||||
|
||||
// # Logout
|
||||
cy.apiLogout();
|
||||
|
||||
// * Verify that the user was redirected to the login page after the logout
|
||||
cy.url().should('include', '/login');
|
||||
|
||||
// * Verify no privacy link
|
||||
cy.findByText('Privacy Policy').should('not.exist');
|
||||
|
||||
// # Visit signup page
|
||||
cy.findByText('Don\'t have an account?', {timeout: TIMEOUTS.HALF_MIN}).should('be.visible').click();
|
||||
|
||||
// * Verify no privacy link
|
||||
cy.get('.hfroute-footer').scrollIntoView().should('be.visible').within(() => {
|
||||
cy.findByText('Privacy Policy').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user