fix(accessibility): tab support at login, reset and signup pages, buttons at ATE and app bar (#24214)

Этот коммит содержится в:
Saturnino Abril
2023-08-10 08:53:13 -04:00
коммит произвёл GitHub
родитель 45a14e23a9
Коммит ecf7cdbdea
26 изменённых файлов: 441 добавлений и 87 удалений

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

@@ -45,10 +45,12 @@ describe('Signup Email page', () => {
it('should match elements, back button', () => {
// * Check elements in the header with back button
cy.get('#back_button').should('be.visible');
cy.get('#back_button').should('contain', 'Back');
cy.get('#back_button_icon').should('be.visible');
cy.get('#back_button_icon').should('have.attr', 'title', 'Back Icon');
cy.findByTestId('back_button').
should('be.visible').
and('have.text', 'Back');
cy.get('#back_button_icon').
should('be.visible').
and('have.attr', 'title', 'Back Icon');
});
it('should match elements, body', () => {
@@ -94,20 +96,20 @@ describe('Signup Email page', () => {
// * Check elements in the footer
cy.get('.hfroute-footer').scrollIntoView().should('be.visible').within(() => {
// * Check if about footer link is present
cy.findByText('About').should('exist').
and('have.attr', 'href', config.SupportSettings.AboutLink || ABOUT_LINK);
cy.findByText('About').should('be.visible').
should('have.attr', 'href').and('match', new RegExp(`${config.SupportSettings.AboutLink || ABOUT_LINK}/*`));
// * Check if privacy footer link is present
cy.findByText('Privacy Policy').should('exist').
and('have.attr', 'href', config.SupportSettings.PrivacyPolicyLink || PRIVACY_POLICY_LINK);
cy.findByText('Privacy Policy').should('be.visible').
should('have.attr', 'href').and('match', new RegExp(`${config.SupportSettings.PrivacyPolicyLink || PRIVACY_POLICY_LINK}/*`));
// * Check if terms footer link is present
cy.findByText('Terms').should('exist').
and('have.attr', 'href', config.SupportSettings.TermsOfServiceLink || TERMS_OF_SERVICE_LINK);
cy.findByText('Terms').should('be.visible').
should('have.attr', 'href').and('match', new RegExp(`${config.SupportSettings.TermsOfServiceLink || TERMS_OF_SERVICE_LINK}/*`));
// * Check if help footer link is present
cy.findByText('Help').should('exist').
and('have.attr', 'href', config.SupportSettings.HelpLink || HELP_LINK);
cy.findByText('Help').should('be.visible').
should('have.attr', 'href').and('match', new RegExp(`${config.SupportSettings.HelpLink || HELP_LINK}/*`));
const todaysDate = new Date();
const currentYear = todaysDate.getFullYear();

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

@@ -18,6 +18,11 @@ type ExtendedFixtures = {
pages: typeof pages;
};
type AxeBuilderOptions = {
disableColorContrast?: boolean;
disableLinkInTextBlock?: boolean;
};
export const test = base.extend<ExtendedFixtures>({
// eslint-disable-next-line no-empty-pattern
axe: async ({}, use) => {
@@ -94,15 +99,29 @@ class PlaywrightExtended {
}
class AxeBuilderExtended {
readonly builder: (page: Page, disableRules?: string[]) => AxeBuilder;
readonly builder: (page: Page, options?: AxeBuilderOptions) => AxeBuilder;
// See https://github.com/dequelabs/axe-core/blob/master/doc/API.md#axe-core-tags
readonly tags: string[] = ['wcag2a', 'wcag2aa'];
constructor() {
// See https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md#wcag-20-level-a--aa-rules
this.builder = (page: Page, disableRules?: string[]) => {
return new AxeBuilder({page}).withTags(this.tags).disableRules(disableRules || []);
this.builder = (page: Page, options: AxeBuilderOptions = {}) => {
// See https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md#wcag-20-level-a--aa-rules
const disabledRules: string[] = [];
if (options.disableColorContrast) {
// Disabled in pages due to impact to overall theme of Mattermost.
// Option: make use of custom theme to improve color contrast.
disabledRules.push('color-contrast');
}
if (options.disableLinkInTextBlock) {
// Disabled in pages due to impact to overall theme of Mattermost.
// Option: make use of custom theme to improve color contrast.
disabledRules.push('link-in-text-block');
}
return new AxeBuilder({page}).withTags(this.tags).disableRules(disabledRules);
};
}

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

@@ -0,0 +1,30 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Locator} from '@playwright/test';
export default class Footer {
readonly container: Locator;
readonly copyright;
readonly aboutLink;
readonly privacyPolicyLink;
readonly termsLink;
readonly helpLink;
constructor(container: Locator) {
this.container = container;
this.copyright = container.locator('.footer-copyright');
this.aboutLink = container.locator('text=About');
this.privacyPolicyLink = container.locator('text=Privacy Policy');
this.termsLink = container.locator('text=Terms');
this.helpLink = container.locator('text=Help');
}
async toBeVisible() {
await expect(this.copyright).toBeVisible();
}
}
export {Footer};

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

@@ -10,7 +10,9 @@ import {ChannelsPost} from './channels/post';
import {ChannelsSidebarLeft} from './channels/sidebar_left';
import {ChannelsSidebarRight} from './channels/sidebar_right';
import {FindChannelsModal} from './channels/find_channels_modal';
import {Footer} from './footer';
import {GlobalHeader} from './global_header';
import {MainHeader} from './main_header';
import {PostDotMenu} from './channels/post_dot_menu';
import {DeletePostModal} from './channels/delete_post_modal';
import {PostMenu} from './channels/post_menu';
@@ -26,7 +28,9 @@ const components = {
ChannelsSidebarLeft,
ChannelsSidebarRight,
FindChannelsModal,
Footer,
GlobalHeader,
MainHeader,
PostDotMenu,
DeletePostModal,
PostMenu,

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

@@ -0,0 +1,24 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Locator} from '@playwright/test';
export default class MainHeader {
readonly container: Locator;
readonly logo;
readonly backButton;
constructor(container: Locator) {
this.container = container;
this.logo = container.locator('.header-logo-link');
this.backButton = container.getByTestId('back_button');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}
export {MainHeader};

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

@@ -6,6 +6,7 @@ import {BoardsViewPage} from './boards_view';
import {ChannelsPage} from './channels';
import {LandingLoginPage} from './landing_login';
import {LoginPage} from './login';
import {ResetPasswordPage} from './reset_password';
import {SignupPage} from './signup';
const pages = {
@@ -14,6 +15,7 @@ const pages = {
ChannelsPage,
LandingLoginPage,
LoginPage,
ResetPasswordPage,
SignupPage,
};

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

@@ -6,6 +6,8 @@ import {expect, Page} from '@playwright/test';
import {AdminConfig} from '@mattermost/types/config';
import {UserProfile} from '@mattermost/types/users';
import {components} from '@e2e-support/ui/components';
export default class LoginPage {
readonly adminConfig: AdminConfig;
@@ -17,6 +19,7 @@ export default class LoginPage {
readonly loginInput;
readonly loginPlaceholder;
readonly passwordInput;
readonly passwordToggleButton;
readonly signInButton;
readonly createAccountLink;
readonly forgotPasswordLink;
@@ -24,6 +27,9 @@ export default class LoginPage {
readonly fieldWithError;
readonly formContainer;
readonly header;
readonly footer;
constructor(page: Page, adminConfig: AdminConfig) {
this.page = page;
this.adminConfig = adminConfig;
@@ -34,16 +40,20 @@ export default class LoginPage {
this.title = page.locator('h1:has-text("Log in to your account")');
this.subtitle = page.locator('text=Collaborate with your team in real-time');
this.bodyCard = page.locator('.login-body-card');
this.bodyCard = page.locator('.login-body-card-content');
this.loginInput = page.locator('#input_loginId');
this.loginPlaceholder = page.locator(`[placeholder="${loginInputPlaceholder}"]`);
this.passwordInput = page.locator('#input_password-input');
this.passwordToggleButton = page.getByRole('button', {name: 'Show or hide password'});
this.signInButton = page.locator('button:has-text("Log in")');
this.createAccountLink = page.locator("text=Don't have an account?");
this.forgotPasswordLink = page.locator('text=Forgot your password?');
this.userErrorLabel = page.locator('text=Please enter your email or username');
this.fieldWithError = page.locator('.with-error');
this.formContainer = page.locator('.signup-team__container');
this.header = new components.MainHeader(page.locator('.hfroute-header'));
this.footer = new components.Footer(page.locator('.hfroute-footer'));
}
async toBeVisible() {

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

@@ -0,0 +1,51 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Page} from '@playwright/test';
import {components} from '@e2e-support/ui/components';
export default class ResetPasswordPage {
readonly page: Page;
readonly title;
readonly subtitle;
readonly emailInput;
readonly resetButton;
readonly formContainer;
readonly header;
readonly footer;
constructor(page: Page) {
this.page = page;
this.title = page.locator('h1:has-text("Password Reset")');
this.subtitle = page.locator('text=To reset your password, enter the email address you used to sign up');
this.emailInput = page.locator(`[placeholder="Email"]`);
this.resetButton = page.locator('#passwordResetButton');
this.formContainer = page.locator('.signup-team__container');
this.header = new components.MainHeader(page.locator('.signup-header'));
this.footer = new components.Footer(page.locator('#footer_section'));
}
async toBeVisible() {
await this.page.waitForLoadState('networkidle');
await expect(this.title).toBeVisible();
await expect(this.subtitle).toBeVisible();
await expect(this.emailInput).toBeVisible();
await expect(this.resetButton).toBeVisible();
}
async goto() {
await this.page.goto('/reset_password');
}
async reset(email: string) {
await this.emailInput.fill(email);
await Promise.all([this.page.waitForNavigation(), this.resetButton.click()]);
}
}
export {ResetPasswordPage};

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

@@ -4,6 +4,7 @@
import {expect, Page} from '@playwright/test';
import {duration, wait} from '@e2e-support/util';
import {components} from '@e2e-support/ui/components';
export default class SignupPage {
readonly page: Page;
@@ -14,28 +15,50 @@ export default class SignupPage {
readonly emailInput;
readonly usernameInput;
readonly passwordInput;
readonly passwordToggleButton;
readonly newsLetterCheckBox;
readonly newsLetterPrivacyPolicyLink;
readonly newsLetterUnsubscribeLink;
readonly agreementTermsOfUseLink;
readonly agreementPrivacyPolicyLink;
readonly createAccountButton;
readonly loginLink;
readonly emailError;
readonly usernameError;
readonly passwordError;
readonly header;
readonly footer;
constructor(page: Page) {
this.page = page;
this.title = page.locator('h1:has-text("Lets get started")');
this.subtitle = page.locator('text=Create your Mattermost account to start collaborating with your team');
this.bodyCard = page.locator('.signup-body-card');
this.bodyCard = page.locator('.signup-body-card-content');
this.loginLink = page.locator('text=Log in');
this.emailInput = page.locator('#input_email');
this.usernameInput = page.locator('#input_name');
this.passwordInput = page.locator('#input_password-input');
this.passwordToggleButton = page.getByRole('button', {name: 'Show or hide password'});
this.createAccountButton = page.locator('button:has-text("Create Account")');
this.loginLink = page.locator('text=Click here to sign in.');
this.emailError = page.locator('text=Please enter a valid email address');
this.usernameError = page.locator(
'text=Usernames have to begin with a lowercase letter and be 3-22 characters long. You can use lowercase letters, numbers, periods, dashes, and underscores.'
);
this.passwordError = page.locator('text=Must be 5-64 characters long.');
const newsletterBlock = page.locator('.check-input');
this.newsLetterCheckBox = newsletterBlock.getByRole('checkbox', {name: 'newsletter checkbox'});
this.newsLetterPrivacyPolicyLink = newsletterBlock.locator('text=Privacy Policy');
this.newsLetterUnsubscribeLink = newsletterBlock.locator('text=unsubscribe');
const agreementBlock = page.locator('.signup-body-card-agreement');
this.agreementTermsOfUseLink = agreementBlock.locator('text=Terms of Use');
this.agreementPrivacyPolicyLink = agreementBlock.locator('text=Privacy Policy');
this.header = new components.MainHeader(page.locator('.hfroute-header'));
this.footer = new components.Footer(page.locator('.hfroute-footer'));
}
async toBeVisible() {
@@ -49,7 +72,7 @@ export default class SignupPage {
}
async goto() {
await this.page.goto('/signup_email');
await this.page.goto('/signup_user_complete');
}
async create(user: {email: string; username: string; password: string}, waitForRedirect = true) {

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

@@ -19,7 +19,7 @@ test('Intro to channel', async ({pw, pages, axe}) => {
// # Analyze the page
// Disable 'color-contrast' to be addressed by MM-53814
const accessibilityScanResults = await axe.builder(page, ['color-contrast']).analyze();
const accessibilityScanResults = await axe.builder(page, {disableColorContrast: true}).analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);

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

@@ -3,7 +3,7 @@
import {expect, test} from '@e2e-support/test_fixture';
test('/login', async ({pw, pages, page, axe}) => {
test('/login accessibility quick check', async ({pw, pages, page, axe}) => {
// # Go to login page
const {adminClient} = await pw.getAdminClient();
const adminConfig = await adminClient.getConfig();
@@ -17,3 +17,63 @@ test('/login', async ({pw, pages, page, axe}) => {
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
});
test('/login accessibility tab support', async ({pw, pages, page}) => {
// # Go to login page
const {adminClient} = await pw.getAdminClient();
const adminConfig = await adminClient.getConfig();
const loginPage = new pages.LoginPage(page, adminConfig);
await loginPage.goto();
await loginPage.toBeVisible();
// * Should have focused at login input on page load
expect(await loginPage.loginInput).toBeFocused();
// * Should move focus to password input after tab
await loginPage.loginInput.press('Tab');
expect(await loginPage.passwordInput).toBeFocused();
// * Should move focus to password toggle button after tab
await loginPage.passwordInput.press('Tab');
expect(await loginPage.passwordToggleButton).toBeFocused();
// * Should move focus to forgot password link after tab
await loginPage.passwordToggleButton.press('Tab');
expect(await loginPage.forgotPasswordLink).toBeFocused();
// * Should move focus to forgot password link after tab
await loginPage.forgotPasswordLink.press('Tab');
expect(await loginPage.signInButton).toBeFocused();
// * Should move focus to about link after tab
await loginPage.signInButton.press('Tab');
expect(await loginPage.footer.aboutLink).toBeFocused();
// * Should move focus to privacy policy link after tab
await loginPage.footer.aboutLink.press('Tab');
expect(await loginPage.footer.privacyPolicyLink).toBeFocused();
// * Should move focus to terms link after tab
await loginPage.footer.privacyPolicyLink.press('Tab');
expect(await loginPage.footer.termsLink).toBeFocused();
// * Should move focus to help link after tab
await loginPage.footer.termsLink.press('Tab');
expect(await loginPage.footer.helpLink).toBeFocused();
// * Should move focus to header logo after tab
await loginPage.footer.helpLink.press('Tab');
expect(await loginPage.header.logo).toBeFocused();
// * Should move focus to create account link after tab
await loginPage.header.logo.press('Tab');
expect(await loginPage.createAccountLink).toBeFocused();
// * Should move focus to create account link after tab
await loginPage.createAccountLink.press('Tab');
expect(await loginPage.bodyCard).toBeFocused();
// * Then, should move focus to login body after tab
await loginPage.bodyCard.press('Tab');
expect(await loginPage.loginInput).toBeFocused();
});

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

@@ -0,0 +1,55 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@e2e-support/test_fixture';
test('/reset_password accessibility quick check', async ({pages, page, axe}) => {
// # Go to reset password page
const resetPasswordPage = new pages.ResetPasswordPage(page);
await resetPasswordPage.goto();
await resetPasswordPage.toBeVisible();
// # Analyze the page
const accessibilityScanResults = await axe.builder(resetPasswordPage.page, {disableColorContrast: true}).analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
});
test('/reset_password accessibility tab support', async ({pages, page}) => {
// # Go to reset password page
const resetPasswordPage = new pages.ResetPasswordPage(page);
await resetPasswordPage.goto();
await resetPasswordPage.toBeVisible();
// * Should have focused at email input on page load
expect(await resetPasswordPage.emailInput).toBeFocused();
// * Should move focus to reset button after tab
await resetPasswordPage.emailInput.press('Tab');
expect(await resetPasswordPage.resetButton).toBeFocused();
// * Should move focus to about link after tab
await resetPasswordPage.resetButton.press('Tab');
expect(await resetPasswordPage.footer.aboutLink).toBeFocused();
// * Should move focus to privacy policy link after tab
await resetPasswordPage.footer.aboutLink.press('Tab');
expect(await resetPasswordPage.footer.privacyPolicyLink).toBeFocused();
// * Should move focus to terms link after tab
await resetPasswordPage.footer.privacyPolicyLink.press('Tab');
expect(await resetPasswordPage.footer.termsLink).toBeFocused();
// * Should move focus to help link after tab
await resetPasswordPage.footer.termsLink.press('Tab');
expect(await resetPasswordPage.footer.helpLink).toBeFocused();
// * Should move focus to header logo after tab
await resetPasswordPage.footer.helpLink.press('Tab');
expect(await resetPasswordPage.header.backButton).toBeFocused();
// * Then, should move focus to email input after tab
await resetPasswordPage.header.backButton.press('Tab');
expect(await resetPasswordPage.emailInput).toBeFocused();
});

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

@@ -0,0 +1,93 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@e2e-support/test_fixture';
test('/signup_user_complete accessibility quick check', async ({pages, page, axe}) => {
// # Go to reset password page
const signupPage = new pages.SignupPage(page);
await signupPage.goto();
await signupPage.toBeVisible();
// # Analyze the page
const accessibilityScanResults = await axe
.builder(signupPage.page, {disableColorContrast: true, disableLinkInTextBlock: true})
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
});
test('/signup_user_complete accessibility tab support', async ({pages, page}) => {
// # Go to reset password page
const signupPage = new pages.SignupPage(page);
await signupPage.goto();
await signupPage.toBeVisible();
// * Should have focused at email input on page load
expect(await signupPage.emailInput).toBeFocused();
// * Should move focus to username input after tab
await signupPage.emailInput.press('Tab');
expect(await signupPage.usernameInput).toBeFocused();
// * Should move focus to password input after tab
await signupPage.usernameInput.press('Tab');
expect(await signupPage.passwordInput).toBeFocused();
// * Should move focus to password toggle button after tab
await signupPage.passwordInput.press('Tab');
expect(await signupPage.passwordToggleButton).toBeFocused();
// * Should move focus to newsletter checkbox after tab
await signupPage.passwordToggleButton.press('Tab');
expect(await signupPage.newsLetterCheckBox).toBeFocused();
// * Should move focus to newsletter privacy policy link after tab
await signupPage.newsLetterCheckBox.press('Tab');
expect(await signupPage.newsLetterPrivacyPolicyLink).toBeFocused();
// * Should move focus to newsletter unsubscribe link after tab
await signupPage.newsLetterPrivacyPolicyLink.press('Tab');
expect(await signupPage.newsLetterUnsubscribeLink).toBeFocused();
// * Should move focus to agreement terms of use link after tab
await signupPage.newsLetterUnsubscribeLink.press('Tab');
expect(await signupPage.agreementTermsOfUseLink).toBeFocused();
// * Should move focus to agreement privacy policy link after tab
await signupPage.agreementTermsOfUseLink.press('Tab');
expect(await signupPage.agreementPrivacyPolicyLink).toBeFocused();
// * Should move focus to privacy policy link after tab
await signupPage.footer.aboutLink.press('Tab');
expect(await signupPage.footer.privacyPolicyLink).toBeFocused();
// * Should move focus to terms link after tab
await signupPage.footer.privacyPolicyLink.press('Tab');
expect(await signupPage.footer.termsLink).toBeFocused();
// * Should move focus to help link after tab
await signupPage.footer.termsLink.press('Tab');
expect(await signupPage.footer.helpLink).toBeFocused();
// * Should move focus to header logo after tab
await signupPage.footer.helpLink.press('Tab');
expect(await signupPage.header.logo).toBeFocused();
// * Should move focus to header back button after tab
await signupPage.header.logo.press('Tab');
expect(await signupPage.header.backButton).toBeFocused();
// * Should move focus to log in link after tab
await signupPage.header.backButton.press('Tab');
expect(await signupPage.loginLink).toBeFocused();
// * Should move focus to sign up body after tab
await signupPage.loginLink.press('Tab');
expect(await signupPage.bodyCard).toBeFocused();
// * Then, should move focus to email input after tab
await signupPage.bodyCard.press('Tab');
expect(await signupPage.emailInput).toBeFocused();
});

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

@@ -71,6 +71,7 @@ export const IconContainer = styled.button`
`;
interface FormattingIconProps {
id?: string;
mode: MarkdownMode;
onClick?: () => void;
className?: string;
@@ -129,7 +130,7 @@ const FormattingIcon = (props: FormattingIconProps): JSX.Element => {
const bodyAction = (
<IconContainer
type='button'
id={`FormattingControl_${mode}`}
id={props.id || `FormattingControl_${mode}`}
onClick={onClick}
aria-label={buttonAriaLabel}
{...otherProps}

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

@@ -50,6 +50,7 @@ const AlertBanner = ({
children,
}: AlertBannerProps) => {
const {formatMessage} = useIntl();
const closeText = formatMessage({id: 'alert_banner.tooltipCloseBtn', defaultMessage: 'Close'});
const [tooltipId] = useState(`alert_banner_close_btn_tooltip_${Math.random()}`);
const bannerIcon = useCallback(() => {
@@ -110,12 +111,11 @@ const AlertBanner = ({
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='left'
overlay={closeBtnTooltip || (
<Tooltip id={tooltipId}>
{formatMessage({id: 'alert_banner.tooltipCloseBtn', defaultMessage: 'Close'})}
</Tooltip>
<Tooltip id={tooltipId}>{closeText}</Tooltip>
)}
>
<button
aria-label={closeText}
className='AlertBanner__closeButton'
onClick={onDismiss}
>

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

@@ -76,6 +76,8 @@ exports[`components/app_bar/app_bar should match snapshot on mount 1`] = `
>
<div
className="app-bar__old-icon app-bar__icon-inner app-bar__icon-inner--centered"
role="button"
tabIndex={0}
>
fallback_component
</div>
@@ -241,6 +243,8 @@ exports[`components/app_bar/app_bar should match snapshot on mount when App Bar
>
<div
className="app-bar__old-icon app-bar__icon-inner app-bar__icon-inner--centered"
role="button"
tabIndex={0}
>
fallback_component
</div>

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

@@ -64,7 +64,11 @@ const AppBarPluginComponent = (props: PluginComponentProps) => {
const iconUrl = component.iconUrl;
let content: React.ReactNode = (
<div className='app-bar__icon-inner'>
<div
role='button'
tabIndex={0}
className='app-bar__icon-inner'
>
<img
src={iconUrl}
onLoad={onImageLoadComplete}
@@ -77,7 +81,11 @@ const AppBarPluginComponent = (props: PluginComponentProps) => {
if (!iconUrl) {
content = (
<div className={classNames('app-bar__old-icon app-bar__icon-inner app-bar__icon-inner--centered', {'app-bar__old-icon--active': isButtonActive})}>
<div
role='button'
tabIndex={0}
className={classNames('app-bar__old-icon app-bar__icon-inner app-bar__icon-inner--centered', {'app-bar__old-icon--active': isButtonActive})}
>
{component.icon}
</div>
);

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

@@ -25,11 +25,9 @@ const BackButton = ({url, className, onClick}: Props): JSX.Element => {
const {formatMessage} = useIntl();
return (
<div
id='back_button'
className={classNames('signup-header', className)}
>
<div className={classNames('signup-header', className)}>
<Link
data-testid='back_button'
onClick={onClick}
to={url}
>

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

@@ -43,10 +43,7 @@ exports[`components/HeaderFooterTemplate should match snapshot with about link 1
key="about_link"
location="header_footer_template"
>
<MemoizedFormattedMessage
defaultMessage="About"
id="web.footer.about"
/>
About
</ExternalLink>
</span>
</div>
@@ -98,10 +95,7 @@ exports[`components/HeaderFooterTemplate should match snapshot with all links 1`
key="about_link"
location="header_footer_template"
>
<MemoizedFormattedMessage
defaultMessage="About"
id="web.footer.about"
/>
About
</ExternalLink>
<ExternalLink
className="footer-link"
@@ -110,10 +104,7 @@ exports[`components/HeaderFooterTemplate should match snapshot with all links 1`
key="privacy_link"
location="header_footer_template"
>
<MemoizedFormattedMessage
defaultMessage="Privacy Policy"
id="web.footer.privacy"
/>
Privacy Policy
</ExternalLink>
<ExternalLink
className="footer-link"
@@ -122,10 +113,7 @@ exports[`components/HeaderFooterTemplate should match snapshot with all links 1`
key="terms_link"
location="header_footer_template"
>
<MemoizedFormattedMessage
defaultMessage="Terms"
id="web.footer.terms"
/>
Terms
</ExternalLink>
<ExternalLink
className="footer-link"
@@ -134,10 +122,7 @@ exports[`components/HeaderFooterTemplate should match snapshot with all links 1`
key="help_link"
location="header_footer_template"
>
<MemoizedFormattedMessage
defaultMessage="Help"
id="web.footer.help"
/>
Help
</ExternalLink>
</span>
</div>
@@ -235,10 +220,7 @@ exports[`components/HeaderFooterTemplate should match snapshot with help link 1`
key="help_link"
location="header_footer_template"
>
<MemoizedFormattedMessage
defaultMessage="Help"
id="web.footer.help"
/>
Help
</ExternalLink>
</span>
</div>
@@ -290,10 +272,7 @@ exports[`components/HeaderFooterTemplate should match snapshot with privacy poli
key="privacy_link"
location="header_footer_template"
>
<MemoizedFormattedMessage
defaultMessage="Privacy Policy"
id="web.footer.privacy"
/>
Privacy Policy
</ExternalLink>
</span>
</div>
@@ -345,10 +324,7 @@ exports[`components/HeaderFooterTemplate should match snapshot with term of serv
key="terms_link"
location="header_footer_template"
>
<MemoizedFormattedMessage
defaultMessage="Terms"
id="web.footer.terms"
/>
Terms
</ExternalLink>
</span>
</div>

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

@@ -3,10 +3,10 @@
import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {ClientConfig} from '@mattermost/types/config';
import ExternalLink from 'components/external_link';
import {localizeMessage} from 'utils/utils';
type Props = {
config: Partial<ClientConfig> | undefined;
@@ -57,10 +57,7 @@ export default class NotLoggedIn extends React.PureComponent<Props> {
location='header_footer_template'
href={this.props.config.AboutLink}
>
<FormattedMessage
id='web.footer.about'
defaultMessage='About'
/>
{localizeMessage('web.footer.about', 'About')}
</ExternalLink>,
);
}
@@ -74,10 +71,7 @@ export default class NotLoggedIn extends React.PureComponent<Props> {
location='header_footer_template'
href={this.props.config.PrivacyPolicyLink}
>
<FormattedMessage
id='web.footer.privacy'
defaultMessage='Privacy Policy'
/>
{localizeMessage('web.footer.privacy', 'Privacy Policy')}
</ExternalLink>,
);
}
@@ -91,10 +85,7 @@ export default class NotLoggedIn extends React.PureComponent<Props> {
location='header_footer_template'
href={this.props.config.TermsOfServiceLink}
>
<FormattedMessage
id='web.footer.terms'
defaultMessage='Terms'
/>
{localizeMessage('web.footer.terms', 'Terms')}
</ExternalLink>,
);
}
@@ -108,10 +99,7 @@ export default class NotLoggedIn extends React.PureComponent<Props> {
location='header_footer_template'
href={this.props.config.HelpLink}
>
<FormattedMessage
id='web.footer.help'
defaultMessage='Help'
/>
{localizeMessage('web.footer.help', 'Help')}
</ExternalLink>,
);
}

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

@@ -363,8 +363,6 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
const onWindowFocus = useCallback(() => {
if (extraParam === Constants.SIGNIN_VERIFIED && emailParam) {
passwordInput.current?.focus();
} else {
loginIdInput.current?.focus();
}
}, [emailParam, extraParam]);

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

@@ -97,6 +97,7 @@ function PostPriorityPickerOverlay({
{...getTooltipReferenceProps()}
>
<IconContainer
id='messagePriority'
ref={pickerRef}
className={classNames({control: true, active: pickerOpen})}
disabled={disabled}

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

@@ -594,6 +594,7 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
return (
<CheckInput
id='signup-body-card-form-check-newsletter'
ariaLabel={formatMessage({id: 'newsletter_optin.checkmark.box', defaultMessage: 'newsletter checkbox'})}
name='newsletter'
onChange={() => setSubscribeToSecurityNewsletter(!subscribeToSecurityNewsletter)}
text={

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

@@ -6,6 +6,7 @@ import './check.scss';
type Props = {
id: string;
ariaLabel: string;
name: string;
text: ReactNode;
onChange: () => void;
@@ -13,14 +14,17 @@ type Props = {
}
function CheckInput(props: Props) {
const {id, ariaLabel, text, ...rest} = props;
return (
<div className='check-input'>
<input
{...props}
data-testid={props.id}
{...rest}
aria-label={ariaLabel}
data-testid={id}
type='checkbox'
/>
<span className='text'>{props.text}</span>
<span className='text'>{text}</span>
</div>
);
}

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

@@ -66,7 +66,7 @@ const PasswordInput = React.forwardRef((
<button
id='password_toggle'
type='button'
aria-label={placeHolder}
aria-label={formatMessage({id: 'widget.passwordInput.passwordToggle', defaultMessage: 'Show or hide password'})}
className='password-input-toggle'
onClick={toggleShowPassword}
disabled={disabled}

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

@@ -4100,6 +4100,7 @@
"navbar.viewPinnedPosts": "View Pinned Posts",
"newChannelWithBoard.tutorialTip.description": "The board you just created can be quickly accessed by clicking on the Boards icon in the App bar. You can view the boards that are linked to this channel in the right-hand sidebar and open one in full view.",
"newChannelWithBoard.tutorialTip.title": "Access linked boards from the App Bar",
"newsletter_optin.checkmark.box": "newsletter checkbox",
"newsletter_optin.checkmark.text": "<span>I would like to receive Mattermost security updates via newsletter.</span> By subscribing, I consent to receive emails from Mattermost with product updates, promotions, and company news. I have read the <a>Privacy Policy</a> and understand that I can <aa>unsubscribe</aa> at any time",
"newsletter_optin.desc": "Sign up at <a>{link}</a>.",
"newsletter_optin.title": "Interested in receiving Mattermost security, product, promotions, and company updates updates via newsletter?",
@@ -5589,6 +5590,7 @@
"widget.input.required": "This field is required",
"widget.passwordInput.createPassword": "Choose a Password",
"widget.passwordInput.password": "Password",
"widget.passwordInput.passwordToggle": "Show or hide password",
"widgets.channels_input.empty": "No channels found",
"widgets.channels_input.loading": "Loading",
"widgets.users_emails_input.loading": "Loading",