diff --git a/e2e-tests/cypress/tests/integration/channels/account_settings/security/password_spec.ts b/e2e-tests/cypress/tests/integration/channels/account_settings/security/password_spec.ts index ff939820b6..e17e538f80 100644 --- a/e2e-tests/cypress/tests/integration/channels/account_settings/security/password_spec.ts +++ b/e2e-tests/cypress/tests/integration/channels/account_settings/security/password_spec.ts @@ -80,8 +80,8 @@ describe('Profile', () => { // # Save cy.uiSave(); - // * Verify for error message: "Must be 5-72 characters long." - cy.get('#clientError').should('be.visible').should('have.text', 'Must be 5-72 characters long.'); + // * Verify for error message: "Your password must be 5-72 characters long." + cy.get('#clientError').should('be.visible').should('have.text', 'Your password must be 5-72 characters long.'); }); it('MM-T2084 Password: Cancel out of password changes causes no changes to be made', () => { diff --git a/e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_2_spec.ts b/e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_2_spec.ts index 95d24b9673..9084008159 100644 --- a/e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_2_spec.ts +++ b/e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_2_spec.ts @@ -49,7 +49,7 @@ describe('Authentication', () => { cy.uiSave(); - cy.findByText('Must be 7-72 characters long.').should('be.visible'); + cy.findByText('Your password must be 7-72 characters long.').should('be.visible'); cy.apiLogout(); @@ -65,7 +65,7 @@ describe('Authentication', () => { cy.findByText('Create Account').click(); // * Assert the error is what is expected; - cy.findByText('Must be 7-72 characters long.').should('be.visible'); + cy.findByText('Your password must be 7-72 characters long.').should('be.visible'); cy.get('#input_password-input').clear().type('greaterthan7'); @@ -118,7 +118,7 @@ describe('Authentication', () => { cy.findByText('Create Account').click(); // * Assert the error is what is expected; - cy.findByText('Must be 5-72 characters long and include both lowercase and uppercase letters, numbers, and special characters.').should('be.visible'); + cy.findByText('Your password must be 5-72 characters long and include both lowercase and uppercase letters, numbers, and special characters.').should('be.visible'); }); }); diff --git a/e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_4_spec.ts b/e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_4_spec.ts index d53c73467e..ad5f76dd2e 100644 --- a/e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_4_spec.ts +++ b/e2e-tests/cypress/tests/integration/channels/auth_sso/authentication_4_spec.ts @@ -58,7 +58,7 @@ describe('Authentication', () => { cy.visit('/login'); // # Remove autofocus from login input - cy.get('.login-body-card-content').should('be.visible').focus(); + cy.get('.login-body-card-title').click(); // # Clear email/username field and type username cy.apiGetClientLicense().then(({isLicensed}) => { diff --git a/e2e-tests/cypress/tests/integration/channels/enterprise/bot_accounts/managing_bot_accounts_spec.ts b/e2e-tests/cypress/tests/integration/channels/enterprise/bot_accounts/managing_bot_accounts_spec.ts index 867d963f49..30c6fcfb6d 100644 --- a/e2e-tests/cypress/tests/integration/channels/enterprise/bot_accounts/managing_bot_accounts_spec.ts +++ b/e2e-tests/cypress/tests/integration/channels/enterprise/bot_accounts/managing_bot_accounts_spec.ts @@ -28,8 +28,8 @@ describe('Managing bot accounts', () => { cy.apiLogout(); cy.visit('/login'); - // # Remove autofocus from login input - cy.get('.login-body-card-content').should('be.visible').focus(); + // # Click on card titile to remove auto focus from the email field + cy.get('.login-body-card-title').click(); // # Enter bot name in the email field cy.findByPlaceholderText('Email, Username or AD/LDAP Username', {timeout: TIMEOUTS.ONE_MIN}).clear().type(botName); diff --git a/e2e-tests/cypress/tests/integration/channels/signin_authentication/login_logout_smoke_spec.js b/e2e-tests/cypress/tests/integration/channels/signin_authentication/login_logout_smoke_spec.js index c104181236..cc2be7f873 100644 --- a/e2e-tests/cypress/tests/integration/channels/signin_authentication/login_logout_smoke_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/signin_authentication/login_logout_smoke_spec.js @@ -22,7 +22,7 @@ describe('SignIn Authentication', () => { cy.visit('/login'); // # Remove autofocus from login input - cy.get('.login-body-card-content').should('be.visible').focus(); + cy.get('.login-body-card-title').click(); }); }); @@ -48,7 +48,7 @@ describe('SignIn Authentication', () => { cy.url().should('include', '/login'); // # Remove autofocus from login input - cy.get('.login-body-card-content').should('be.visible').focus(); + cy.get('.login-body-card-title').click(); // # Enter actual user's username in the email field cy.findByPlaceholderText(loginPlaceholder).clear().type(testUser.username); diff --git a/e2e-tests/cypress/tests/integration/channels/signin_authentication/login_open_server_spec.js b/e2e-tests/cypress/tests/integration/channels/signin_authentication/login_open_server_spec.js index bfb0f199d4..4170b17430 100644 --- a/e2e-tests/cypress/tests/integration/channels/signin_authentication/login_open_server_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/signin_authentication/login_open_server_spec.js @@ -46,8 +46,8 @@ describe('Login page with open server', () => { // * Verify title of the document is correct cy.title().should('include', config.TeamSettings.SiteName); - // # Remove autofocus from login id input - cy.get('.login-body-card-content').should('be.visible').focus(); + // # Remove autofocus from login input + cy.get('.login-body-card-title').click(); // * Verify email/username field is present cy.findByPlaceholderText('Email or Username').should('exist').and('be.visible'); @@ -156,6 +156,9 @@ describe('Login page with open server', () => { it('MM-T3306_7 Should show error with invalid password', () => { const invalidPassword = `${Date.now()}-password`; + // Remove autofocus from login input + cy.get('.login-body-card-title').click(); + // # Lets verify generated password is not an actual password expect(invalidPassword).to.not.equal(testUser.password); @@ -173,6 +176,9 @@ describe('Login page with open server', () => { }); it('MM-T3306_8 Should login with a valid email and password and logout', () => { + // Remove autofocus from login input + cy.get('.login-body-card-title').click(); + // # Enter actual users email/username in the email field cy.findByPlaceholderText('Email or Username').clear().type(testUser.username); @@ -196,8 +202,8 @@ describe('Login page with open server', () => { // # Visit login page cy.visit('/login'); - // # Remove autofocus from login id input - cy.get('.login-body-card-content').should('be.visible').focus(); + // # Remove autofocus from login input + cy.get('.login-body-card-title').click(); // # Enter actual users email/username in the email field cy.findByPlaceholderText('Email or Username').clear().type(testUser.username); diff --git a/e2e-tests/cypress/tests/integration/channels/signin_authentication/signup_spec.js b/e2e-tests/cypress/tests/integration/channels/signin_authentication/signup_spec.js index 4cc15da233..71db9fb3fe 100644 --- a/e2e-tests/cypress/tests/integration/channels/signin_authentication/signup_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/signin_authentication/signup_spec.js @@ -75,7 +75,7 @@ describe('Signup Email page', () => { cy.findByText('You can use lowercase letters, numbers, periods, dashes, and underscores.').should('be.visible'); cy.get('#input_password-input').should('be.visible').and('have.attr', 'placeholder', 'Choose a Password'); - cy.findByText('Must be 5-72 characters long.').should('be.visible'); + cy.findByText('Your password must be 5-72 characters long.').should('be.visible'); cy.get('#saveSetting').scrollIntoView().should('be.visible'); cy.get('#saveSetting').should('contain', 'Create Account'); diff --git a/e2e-tests/cypress/tests/integration/channels/system_console/user_management/users_spec.js b/e2e-tests/cypress/tests/integration/channels/system_console/user_management/users_spec.js index d89ca3e74e..e6041c4f6d 100644 --- a/e2e-tests/cypress/tests/integration/channels/system_console/user_management/users_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/system_console/user_management/users_spec.js @@ -164,7 +164,7 @@ describe('System Console > User Management > Users', () => { // * Verify the appropriate error is returned. cy.get('form.form-horizontal').find('.has-error p.error').should('be.visible'). - and('contain', 'Must be 5-72 characters long.'); + and('contain', 'Your password must be 5-72 characters long.'); }); it('MM-T936 Users - System admin changes own password - Blank fields', () => { @@ -193,7 +193,7 @@ describe('System Console > User Management > Users', () => { // * Verify the appropriate error is returned. cy.get('form.form-horizontal').find('.has-error p.error').should('be.visible'). - and('contain', 'Must be 5-72 characters long.'); + and('contain', 'Your password must be 5-72 characters long.'); }); it('MM-T937 Users - System admin changes own password - Successfully changed', () => { diff --git a/e2e-tests/cypress/tests/support/common_login_commands.js b/e2e-tests/cypress/tests/support/common_login_commands.js index f732258575..04d1149679 100644 --- a/e2e-tests/cypress/tests/support/common_login_commands.js +++ b/e2e-tests/cypress/tests/support/common_login_commands.js @@ -5,7 +5,7 @@ import * as TIMEOUTS from '../fixtures/timeouts'; Cypress.Commands.add('checkLoginPage', (settings = {}) => { // # Remove autofocus from login input - cy.get('.login-body-card-content').should('be.visible').focus(); + cy.get('.login-body-card-title').click(); // * Check elements in the body cy.get('#input_loginId', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').and(($loginTextbox) => { diff --git a/e2e-tests/playwright/lib/src/ui/pages/login.ts b/e2e-tests/playwright/lib/src/ui/pages/login.ts index 2f662aa2e0..a1397f8350 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/login.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/login.ts @@ -37,7 +37,7 @@ export default class LoginPage { this.loginPlaceholder = page.locator(`[placeholder="Email or Username"]`); this.loginWithAdLdapPlaceholder = page.locator(`[placeholder="Email, Username or AD/LDAP Username"]`); this.passwordInput = page.locator('#input_password-input'); - this.passwordToggleButton = page.getByRole('button', {name: 'Show or hide password'}); + this.passwordToggleButton = page.locator('#password_toggle'); 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?'); diff --git a/e2e-tests/playwright/lib/src/ui/pages/signup.ts b/e2e-tests/playwright/lib/src/ui/pages/signup.ts index 974e3a21a9..cb43497b3b 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/signup.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/signup.ts @@ -40,7 +40,7 @@ export default class SignupPage { 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.passwordToggleButton = page.locator('#password_toggle'); this.createAccountButton = page.locator('button:has-text("Create Account")'); this.emailError = page.locator('text=Please enter a valid email address'); this.usernameError = page.locator( diff --git a/e2e-tests/playwright/specs/accessibility/common/login.spec.ts b/e2e-tests/playwright/specs/accessibility/common/login.spec.ts index ce50a4a9ab..dc4f346370 100644 --- a/e2e-tests/playwright/specs/accessibility/common/login.spec.ts +++ b/e2e-tests/playwright/specs/accessibility/common/login.spec.ts @@ -67,10 +67,6 @@ test('/login accessibility tab support', async ({pw}) => { // * Should move focus to login body after shift+tab await pw.loginPage.loginInput.press('Shift+Tab'); - expect(await pw.loginPage.bodyCard).toBeFocused(); - - // * Should move focus to create account link after shift+tab - await pw.loginPage.bodyCard.press('Shift+Tab'); expect(await pw.loginPage.createAccountLink).toBeFocused(); // * Should move focus to login body after tab diff --git a/e2e-tests/playwright/specs/accessibility/common/signup_user_complete.spec.ts b/e2e-tests/playwright/specs/accessibility/common/signup_user_complete.spec.ts index 2430c7c0fa..e0456404b6 100644 --- a/e2e-tests/playwright/specs/accessibility/common/signup_user_complete.spec.ts +++ b/e2e-tests/playwright/specs/accessibility/common/signup_user_complete.spec.ts @@ -79,14 +79,6 @@ test('/signup_user_complete accessibility tab support', async ({pw}, testInfo) = await pw.signupPage.emailInput.focus(); expect(await pw.signupPage.emailInput).toBeFocused(); - // * Should move focus to sign up body after shift+tab - await pw.signupPage.emailInput.press('Shift+Tab'); - expect(await pw.signupPage.bodyCard).toBeFocused(); - - // * Should move focus to sign up body after shift+tab - await pw.signupPage.emailInput.press('Shift+Tab'); - expect(await pw.signupPage.bodyCard).toBeFocused(); - if (testInfo.project.name === 'ipad') { // * Should move focus to header back button after shift+tab await pw.signupPage.bodyCard.press('Shift+Tab'); diff --git a/e2e-tests/playwright/specs/visual/common/login.spec.ts-snapshots/login-error-chrome-linux.png b/e2e-tests/playwright/specs/visual/common/login.spec.ts-snapshots/login-error-chrome-linux.png index 73b721da62..862c67ab70 100644 Binary files a/e2e-tests/playwright/specs/visual/common/login.spec.ts-snapshots/login-error-chrome-linux.png and b/e2e-tests/playwright/specs/visual/common/login.spec.ts-snapshots/login-error-chrome-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/common/login.spec.ts-snapshots/login-error-firefox-linux.png b/e2e-tests/playwright/specs/visual/common/login.spec.ts-snapshots/login-error-firefox-linux.png index 8ed74c00e6..caab795ccb 100644 Binary files a/e2e-tests/playwright/specs/visual/common/login.spec.ts-snapshots/login-error-firefox-linux.png and b/e2e-tests/playwright/specs/visual/common/login.spec.ts-snapshots/login-error-firefox-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/common/login.spec.ts-snapshots/login-error-ipad-linux.png b/e2e-tests/playwright/specs/visual/common/login.spec.ts-snapshots/login-error-ipad-linux.png index 650eaf69af..784cab742c 100644 Binary files a/e2e-tests/playwright/specs/visual/common/login.spec.ts-snapshots/login-error-ipad-linux.png and b/e2e-tests/playwright/specs/visual/common/login.spec.ts-snapshots/login-error-ipad-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-chrome-linux.png b/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-chrome-linux.png index 06ea901c3f..bc0be8b970 100644 Binary files a/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-chrome-linux.png and b/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-chrome-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-error-chrome-linux.png b/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-error-chrome-linux.png index d5972f27e2..87ad4bd352 100644 Binary files a/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-error-chrome-linux.png and b/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-error-chrome-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-error-firefox-linux.png b/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-error-firefox-linux.png index a12f34d332..138a562785 100644 Binary files a/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-error-firefox-linux.png and b/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-error-firefox-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-error-ipad-linux.png b/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-error-ipad-linux.png index 7dbe0fe185..c39669b25b 100644 Binary files a/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-error-ipad-linux.png and b/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-error-ipad-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-firefox-linux.png b/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-firefox-linux.png index a7da013cb6..965b46f964 100644 Binary files a/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-firefox-linux.png and b/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-firefox-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-ipad-linux.png b/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-ipad-linux.png index 696a505df6..dd50811e75 100644 Binary files a/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-ipad-linux.png and b/e2e-tests/playwright/specs/visual/common/signup_email.spec.ts-snapshots/signup-email-ipad-linux.png differ diff --git a/webapp/channels/src/components/alert_banner/index.tsx b/webapp/channels/src/components/alert_banner/index.tsx index 8af18a1126..e659815763 100644 --- a/webapp/channels/src/components/alert_banner/index.tsx +++ b/webapp/channels/src/components/alert_banner/index.tsx @@ -81,6 +81,7 @@ const AlertBanner = ({ return (
{ @@ -277,6 +278,35 @@ describe('components/login/Login', () => { }); }); + it('should focus username field when there is an error', async () => { + const state = mergeObjects(baseState, { + entities: { + general: { + config: { + EnableSignInWithEmail: 'true', + }, + }, + }, + }); + + renderWithContext( + , + state, + { + intlMessages: { + 'login.noEmail': 'Please enter your email', + }, + }, + ); + + // Try to submit without entering username + screen.getByRole('button', {name: 'Log in'}).click(); + + // Verify username field is focused + const usernameInput = screen.getByLabelText('Email'); + expect(usernameInput).toHaveFocus(); + }); + it('should handle openid text and color props', () => { const state = mergeObjects(baseState, { entities: { diff --git a/webapp/channels/src/components/login/login.tsx b/webapp/channels/src/components/login/login.tsx index 5668c029a7..5283ae5de1 100644 --- a/webapp/channels/src/components/login/login.tsx +++ b/webapp/channels/src/components/login/login.tsx @@ -460,6 +460,12 @@ const Login = ({onCustomizeHeader}: LoginProps) => { }; }, []); + useEffect(() => { + if (hasError) { + loginIdInput.current?.focus(); + } + }, [hasError]); + if (initializing) { return (); } @@ -834,7 +840,6 @@ const Login = ({onCustomizeHeader}: LoginProps) => {

{getCardTitle()} @@ -842,6 +847,7 @@ const Login = ({onCustomizeHeader}: LoginProps) => { {enableCustomBrand && getMessageSubtitle()} {alertBanner && ( { >

{ placeholder={getInputPlaceholder()} disabled={isWaiting} autoFocus={true} + aria-describedby={alertBanner ? 'login-body-card-banner' : undefined} /> { wrapper.find('form').simulate('submit', {preventDefault: () => {}}); expect(baseProps.actions.activateMfa).toBeCalledWith('testcodeinput'); }); + + test('should focus input when code is empty', async () => { + const wrapper = mountWithIntl( + , + ); + const input = wrapper.find('input').getDOMNode() as HTMLInputElement; + const focusSpy = jest.spyOn(input, 'focus'); + + wrapper.find('form').simulate('submit', {preventDefault: () => {}}); + expect(focusSpy).toHaveBeenCalled(); + }); + + test('should focus input when authentication fails', async () => { + const props = { + ...baseProps, + actions: { + ...baseProps.actions, + activateMfa: jest.fn().mockImplementation(() => Promise.resolve({ + error: { + server_error_id: 'ent.mfa.activate.authenticate.app_error', + message: 'Invalid code', + }, + })), + }, + }; + + const wrapper = mountWithIntl( + , + ); + const input = wrapper.find('input').getDOMNode() as HTMLInputElement; + const focusSpy = jest.spyOn(input, 'focus'); + + (wrapper.instance() as Setup).input.current!.value = 'invalidcode'; + wrapper.find('form').simulate('submit', {preventDefault: () => {}}); + await new Promise((resolve) => setTimeout(resolve, 0)); // Wait for state update + expect(focusSpy).toHaveBeenCalled(); + }); }); diff --git a/webapp/channels/src/components/mfa/setup/setup.tsx b/webapp/channels/src/components/mfa/setup/setup.tsx index f14ea689e2..8055263a3a 100644 --- a/webapp/channels/src/components/mfa/setup/setup.tsx +++ b/webapp/channels/src/components/mfa/setup/setup.tsx @@ -105,6 +105,7 @@ export default class Setup extends React.PureComponent { /> ), }); + this.input?.current?.focus(); return; } @@ -126,6 +127,7 @@ export default class Setup extends React.PureComponent { error: error.message, }); } + this.input?.current?.focus(); return; } @@ -137,7 +139,15 @@ export default class Setup extends React.PureComponent { let formClass = 'form-group'; let errorContent; if (this.state.error) { - errorContent =
; + errorContent = ( + + ); formClass += ' has-error'; } @@ -212,6 +222,7 @@ export default class Setup extends React.PureComponent { className='form-control' placeholder={defineMessage({id: 'mfa.setup.code', defaultMessage: 'MFA Code'})} autoFocus={true} + aria-describedby={this.state.error ? 'mfa-setup-error' : undefined} />

{errorContent} diff --git a/webapp/channels/src/components/signup/__snapshots__/signup.test.tsx.snap b/webapp/channels/src/components/signup/__snapshots__/signup.test.tsx.snap index e024825107..6b236386eb 100644 --- a/webapp/channels/src/components/signup/__snapshots__/signup.test.tsx.snap +++ b/webapp/channels/src/components/signup/__snapshots__/signup.test.tsx.snap @@ -35,8 +35,6 @@ exports[`components/signup/Signup should match snapshot for all signup options e >

Create your account

-
-
+
@@ -275,21 +274,20 @@ exports[`components/signup/Signup should match snapshot for all signup options e >

Create your account

-
-
+
diff --git a/webapp/channels/src/components/signup/signup.test.tsx b/webapp/channels/src/components/signup/signup.test.tsx index e9b2953477..0e4c3ac653 100644 --- a/webapp/channels/src/components/signup/signup.test.tsx +++ b/webapp/channels/src/components/signup/signup.test.tsx @@ -18,7 +18,7 @@ import Input from 'components/widgets/inputs/input/input'; import PasswordInput from 'components/widgets/inputs/password_input/password_input'; import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import {act, renderWithContext, screen} from 'tests/react_testing_utils'; +import {act, renderWithContext, screen, fireEvent, waitFor} from 'tests/react_testing_utils'; import {WindowSizes} from 'utils/constants'; import type {GlobalState} from 'types/store'; @@ -274,6 +274,67 @@ describe('components/signup/Signup', () => { expect(wrapper.find(PasswordInput).first().props().disabled).toEqual(true); }); + it('should focus email input when email validation fails', async () => { + renderWithContext(, mockState); + + const emailInput = screen.getByTestId('signup-body-card-form-email-input'); + const submitButton = screen.getByText('Create account'); + + // Submit with invalid email + fireEvent.change(emailInput, {target: {value: 'invalid-email'}}); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(emailInput).toHaveFocus(); + }); + }); + + it('should focus password input when password validation fails', async () => { + renderWithContext(, mockState); + + const emailInput = screen.getByTestId('signup-body-card-form-email-input'); + const usernameInput = screen.getByTestId('signup-body-card-form-name-input'); + const passwordInput = screen.getByTestId('signup-body-card-form-password-input'); + const submitButton = screen.getByText('Create account'); + + // Submit with valid email and username but invalid password + fireEvent.change(emailInput, {target: {value: 'test@example.com'}}); + fireEvent.change(usernameInput, {target: {value: 'testuser'}}); + fireEvent.change(passwordInput, {target: {value: '123'}}); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(passwordInput).toHaveFocus(); + }); + }); + + it('should focus username input when server returns username exists error', async () => { + mockDispatch = jest.fn().mockImplementation(() => Promise.resolve({ + data: {}, + error: { + server_error_id: 'app.user.save.username_exists.app_error', + message: 'Username already exists', + }, + })); + + renderWithContext(, mockState); + + const emailInput = screen.getByTestId('signup-body-card-form-email-input'); + const usernameInput = screen.getByTestId('signup-body-card-form-name-input'); + const passwordInput = screen.getByTestId('signup-body-card-form-password-input'); + const submitButton = screen.getByText('Create account'); + + // Submit with valid data that will trigger server error + fireEvent.change(emailInput, {target: {value: 'test@example.com'}}); + fireEvent.change(usernameInput, {target: {value: 'existinguser'}}); + fireEvent.change(passwordInput, {target: {value: 'password123'}}); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(usernameInput).toHaveFocus(); + }); + }); + it('should add user to team and redirect when team invite valid and logged in', async () => { mockLocation.search = '?id=ppni7a9t87fn3j4d56rwocdctc'; diff --git a/webapp/channels/src/components/signup/signup.tsx b/webapp/channels/src/components/signup/signup.tsx index 3bab859096..f10e3f31da 100644 --- a/webapp/channels/src/components/signup/signup.tsx +++ b/webapp/channels/src/components/signup/signup.tsx @@ -142,6 +142,7 @@ const Signup = ({onCustomizeHeader}: SignupProps) => { const [alertBanner, setAlertBanner] = useState(null); const [isMobileView, setIsMobileView] = useState(false); const [subscribeToSecurityNewsletter, setSubscribeToSecurityNewsletter] = useState(false); + const [submitClicked, setSubmitClicked] = useState(false); const cwsAvailability = useCWSAvailabilityCheck(); @@ -368,6 +369,19 @@ const Signup = ({onCustomizeHeader}: SignupProps) => { } }, [onCustomizeHeader, handleHeaderBackButtonOnClick, isMobileView, getAlternateLink, search]); + useEffect(() => { + if (submitClicked) { + if (emailError && emailInput.current) { + emailInput.current.focus(); + } else if (nameError && nameInput.current) { + nameInput.current.focus(); + } else if (passwordError && passwordInput.current) { + passwordInput.current.focus(); + } + setSubmitClicked(false); + } + }, [emailError, nameError, passwordError, submitClicked]); + if (loading) { return (); } @@ -376,12 +390,6 @@ const Signup = ({onCustomizeHeader}: SignupProps) => { setBrandImageError(true); }; - const onEnterKeyDown = (e: React.KeyboardEvent) => { - if (e.key === Constants.KeyCodes.ENTER[0] && canSubmit) { - handleSubmit(e); - } - }; - const getCardTitle = () => { if (CustomDescriptionText) { return CustomDescriptionText; @@ -579,6 +587,7 @@ const Signup = ({onCustomizeHeader}: SignupProps) => { e.preventDefault(); sendSignUpTelemetryEvents('click_create_account', getRoleFromTrackFlow()); setIsWaiting(true); + setSubmitClicked(true); if (isUserValid()) { setNameError(''); @@ -604,6 +613,12 @@ const Signup = ({onCustomizeHeader}: SignupProps) => { onDismiss: dismissAlert, }); setIsWaiting(false); + + // Special case for accessibility to show the error message when the username is already taken + if (error.server_error_id === 'app.user.save.username_exists.app_error') { + setNameError(error.message); + setSubmitClicked(true); + } return; } @@ -785,8 +800,6 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {

{getCardTitle()} @@ -801,8 +814,9 @@ const Signup = ({onCustomizeHeader}: SignupProps) => { /> )} {enableSignUpWithEmail && ( -

+
{ onBlur={(e) => handleOnBlur(e, 'email')} /> { onBlur={(e) => handleOnBlur(e, 'username')} /> { defaultMessage={formatMessage({id: 'signup_user_completed.create', defaultMessage: 'Create account'})} savingMessage={formatMessage({id: 'signup_user_completed.saving', defaultMessage: 'Creating account…'})} /> -
+ )} {enableSignUpWithEmail && enableExternalSignup && (
diff --git a/webapp/channels/src/components/widgets/inputs/password_input/password_input.tsx b/webapp/channels/src/components/widgets/inputs/password_input/password_input.tsx index 0856ec0bfd..e583f302d2 100644 --- a/webapp/channels/src/components/widgets/inputs/password_input/password_input.tsx +++ b/webapp/channels/src/components/widgets/inputs/password_input/password_input.tsx @@ -40,6 +40,7 @@ const PasswordInput = React.forwardRef(( createMode, disabled, inputSize, + ...otherProps }: PasswordInputProps, ref?: React.Ref, ) => { @@ -66,7 +67,10 @@ const PasswordInput = React.forwardRef((