diff --git a/webapp/channels/src/components/create_team/components/__snapshots__/display_name.test.tsx.snap b/webapp/channels/src/components/create_team/components/__snapshots__/display_name.test.tsx.snap index f9c854cc48..89cbc22c01 100644 --- a/webapp/channels/src/components/create_team/components/__snapshots__/display_name.test.tsx.snap +++ b/webapp/channels/src/components/create_team/components/__snapshots__/display_name.test.tsx.snap @@ -8,13 +8,15 @@ exports[`/components/create_team/components/display_name should match snapshot 1 className="signup-team-logo" src="logo.png" /> -
+
+
@@ -25,6 +27,7 @@ exports[`/components/create_team/components/display_name should match snapshot 1 className="col-sm-9" > { />, ); }); + + test('should focus input when validation error occurs', () => { + const wrapper = mountWithIntl(); + const input = wrapper.find('.form-control').getDOMNode() as HTMLInputElement; + const focusSpy = jest.spyOn(input, 'focus'); + + // Trigger validation error + input.value = ''; + wrapper.find('.form-control').simulate('change'); + wrapper.find('button').simulate('click', { + preventDefault: () => jest.fn(), + }); + + expect(focusSpy).toHaveBeenCalled(); + }); }); diff --git a/webapp/channels/src/components/create_team/components/display_name.tsx b/webapp/channels/src/components/create_team/components/display_name.tsx index c47ce84024..f0f79c2172 100644 --- a/webapp/channels/src/components/create_team/components/display_name.tsx +++ b/webapp/channels/src/components/create_team/components/display_name.tsx @@ -36,8 +36,11 @@ type State = { } export default class TeamSignupDisplayNamePage extends React.PureComponent { + teamNameInput: React.RefObject; + constructor(props: Props) { super(props); + this.teamNameInput = React.createRef(); this.state = { teamDisplayName: this.props.state.team?.display_name || '', @@ -59,6 +62,7 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent), }); + this.teamNameInput.current?.focus(); return; } else if (displayName.length < Constants.MIN_TEAMNAME_LENGTH || displayName.length > Constants.MAX_TEAMNAME_LENGTH) { this.setState({nameError: ( @@ -71,6 +75,7 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent), }); + this.teamNameInput.current?.focus(); return; } @@ -94,7 +99,15 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent{this.state.nameError}; + nameError = ( + + ); nameDivClass += ' has-error'; } @@ -106,19 +119,20 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent -
+
+
diff --git a/webapp/channels/src/components/create_team/components/team_url/__snapshots__/team_url.test.tsx.snap b/webapp/channels/src/components/create_team/components/team_url/__snapshots__/team_url.test.tsx.snap index def7605c61..5688b870b9 100644 --- a/webapp/channels/src/components/create_team/components/team_url/__snapshots__/team_url.test.tsx.snap +++ b/webapp/channels/src/components/create_team/components/team_url/__snapshots__/team_url.test.tsx.snap @@ -8,13 +8,15 @@ exports[`/components/create_team/components/display_name should match snapshot 1 className="signup-team-logo" src="logo.png" /> -
+
+
@@ -37,6 +39,7 @@ exports[`/components/create_team/components/display_name should match snapshot 1 { expect(screen.getByText('Please try another.', {exact: false})).toBeInTheDocument(); }); + + test('should focus input when validation error occurs', () => { + renderWithContext( + , + ); + + const input = screen.getByRole('textbox'); + userEvent.clear(input); + const focusSpy = jest.spyOn(input, 'focus'); + + // Trigger validation error by submitting empty input + screen.getByText('Finish').click(); + + expect(focusSpy).toHaveBeenCalled(); + }); }); diff --git a/webapp/channels/src/components/create_team/components/team_url/team_url.tsx b/webapp/channels/src/components/create_team/components/team_url/team_url.tsx index 9b3f44c44c..2ee2843ae4 100644 --- a/webapp/channels/src/components/create_team/components/team_url/team_url.tsx +++ b/webapp/channels/src/components/create_team/components/team_url/team_url.tsx @@ -57,9 +57,11 @@ type Props = { } export default class TeamUrl extends React.PureComponent { + teamURLInput: React.RefObject; + constructor(props: Props) { super(props); - + this.teamURLInput = React.createRef(); this.state = { nameError: '', isLoading: false, @@ -95,6 +97,7 @@ export default class TeamUrl extends React.PureComponent { defaultMessage='This field is required' />), }); + this.teamURLInput.current?.focus(); return; } @@ -109,6 +112,7 @@ export default class TeamUrl extends React.PureComponent { }} />), }); + this.teamURLInput.current?.focus(); return; } @@ -119,6 +123,7 @@ export default class TeamUrl extends React.PureComponent { defaultMessage="Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash." />), }); + this.teamURLInput.current?.focus(); return; } @@ -191,7 +196,15 @@ export default class TeamUrl extends React.PureComponent { let nameError = null; let nameDivClass = 'form-group'; if (this.state.nameError) { - nameError = ; + nameError = ( + + ); nameDivClass += ' has-error'; } @@ -221,13 +234,13 @@ export default class TeamUrl extends React.PureComponent { className='signup-team-logo' src={logoImage} /> -
+
+
@@ -242,6 +255,7 @@ export default class TeamUrl extends React.PureComponent { { onFocus={this.handleFocus} onChange={this.handleTeamURLInputChange} spellCheck='false' + aria-describedby='teamURLInputError' />
diff --git a/webapp/channels/src/components/create_team/create_team.test.tsx b/webapp/channels/src/components/create_team/create_team.test.tsx index b61074859c..57bfaa55ec 100644 --- a/webapp/channels/src/components/create_team/create_team.test.tsx +++ b/webapp/channels/src/components/create_team/create_team.test.tsx @@ -5,7 +5,7 @@ import {shallow} from 'enzyme'; import React from 'react'; import {FormattedMessage} from 'react-intl'; -import CreateTeam from './create_team'; +import {CreateTeam} from './create_team'; describe('component/create_team', () => { const baseProps = { @@ -25,6 +25,7 @@ describe('component/create_team', () => { }, history: jest.fn(), location: jest.fn(), + intl: {formatMessage: jest.fn()}, } as any; test('should match snapshot default', () => { diff --git a/webapp/channels/src/components/create_team/create_team.tsx b/webapp/channels/src/components/create_team/create_team.tsx index 1d30831a76..c1c14988f4 100644 --- a/webapp/channels/src/components/create_team/create_team.tsx +++ b/webapp/channels/src/components/create_team/create_team.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React from 'react'; -import {FormattedMessage} from 'react-intl'; +import {FormattedMessage, injectIntl, type IntlShape} from 'react-intl'; import {Route, Switch, Redirect} from 'react-router-dom'; import type {RouteComponentProps} from 'react-router-dom'; @@ -48,6 +48,7 @@ export type Props = { isCloud: boolean; isFreeTrial: boolean; usageDeltas: CloudUsage; + intl: IntlShape; }; type State = { @@ -55,7 +56,7 @@ type State = { wizard: string; }; -export default class CreateTeam extends React.PureComponent { +export class CreateTeam extends React.PureComponent { public constructor(props: Props & RouteComponentProps) { super(props); @@ -65,6 +66,16 @@ export default class CreateTeam extends React.PureComponent { this.setState(state); this.props.history.push('/create_team/' + state.wizard); @@ -156,3 +167,5 @@ export default class CreateTeam extends React.PureComponent