[MM-62974][MM-62984][MM-63016][MM-63025] Various fixes for the Create Team screen (#30905)
* [MM-62974] Focus team name inputs when error occurs * [MM-62984] Change h5 to label on create team screens * [MM-63016] Add a page title for the create team page * [MM-63025] Add role=alert and aria-describedby for error messages on create team page * Fix i18n
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
ee61301b67
Коммит
7efb83396b
@@ -8,13 +8,15 @@ exports[`/components/create_team/components/display_name should match snapshot 1
|
||||
className="signup-team-logo"
|
||||
src="logo.png"
|
||||
/>
|
||||
<h5>
|
||||
<label
|
||||
htmlFor="teamNameInput"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Team Name"
|
||||
id="create_team.display_name.teamName"
|
||||
tagName="strong"
|
||||
/>
|
||||
</h5>
|
||||
</label>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
@@ -25,6 +27,7 @@ exports[`/components/create_team/components/display_name should match snapshot 1
|
||||
className="col-sm-9"
|
||||
>
|
||||
<input
|
||||
aria-describedby="teamNameInputError"
|
||||
autoFocus={true}
|
||||
className="form-control"
|
||||
id="teamNameInput"
|
||||
|
||||
@@ -169,4 +169,19 @@ describe('/components/create_team/components/display_name', () => {
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
test('should focus input when validation error occurs', () => {
|
||||
const wrapper = mountWithIntl(<DisplayName {...defaultProps}/>);
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,8 +36,11 @@ type State = {
|
||||
}
|
||||
|
||||
export default class TeamSignupDisplayNamePage extends React.PureComponent<Props, State> {
|
||||
teamNameInput: React.RefObject<HTMLInputElement>;
|
||||
|
||||
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<Props
|
||||
defaultMessage='This field is required'
|
||||
/>),
|
||||
});
|
||||
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<Props
|
||||
}}
|
||||
/>),
|
||||
});
|
||||
this.teamNameInput.current?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,7 +99,15 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent<Props
|
||||
let nameError = null;
|
||||
let nameDivClass = 'form-group';
|
||||
if (this.state.nameError) {
|
||||
nameError = <label className='control-label'>{this.state.nameError}</label>;
|
||||
nameError = (
|
||||
<label
|
||||
role='alert'
|
||||
className='control-label'
|
||||
id='teamNameInputError'
|
||||
>
|
||||
{this.state.nameError}
|
||||
</label>
|
||||
);
|
||||
nameDivClass += ' has-error';
|
||||
}
|
||||
|
||||
@@ -106,19 +119,20 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent<Props
|
||||
className='signup-team-logo'
|
||||
src={logoImage}
|
||||
/>
|
||||
<h5>
|
||||
<label htmlFor='teamNameInput'>
|
||||
<FormattedMessage
|
||||
id='create_team.display_name.teamName'
|
||||
tagName='strong'
|
||||
defaultMessage='Team Name'
|
||||
/>
|
||||
</h5>
|
||||
</label>
|
||||
<div className={nameDivClass}>
|
||||
<div className='row'>
|
||||
<div className='col-sm-9'>
|
||||
<input
|
||||
id='teamNameInput'
|
||||
type='text'
|
||||
ref={this.teamNameInput}
|
||||
className='form-control'
|
||||
placeholder=''
|
||||
maxLength={128}
|
||||
@@ -127,6 +141,7 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent<Props
|
||||
onFocus={this.handleFocus}
|
||||
onChange={this.handleDisplayNameChange}
|
||||
spellCheck='false'
|
||||
aria-describedby='teamNameInputError'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,13 +8,15 @@ exports[`/components/create_team/components/display_name should match snapshot 1
|
||||
className="signup-team-logo"
|
||||
src="logo.png"
|
||||
/>
|
||||
<h5>
|
||||
<label
|
||||
htmlFor="teamURLInput"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Team URL"
|
||||
id="create_team.team_url.teamUrl"
|
||||
tagName="strong"
|
||||
/>
|
||||
</h5>
|
||||
</label>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
@@ -37,6 +39,7 @@ exports[`/components/create_team/components/display_name should match snapshot 1
|
||||
</span>
|
||||
</WithTooltip>
|
||||
<input
|
||||
aria-describedby="teamURLInputError"
|
||||
autoFocus={true}
|
||||
className="form-control"
|
||||
id="teamURLInput"
|
||||
|
||||
@@ -126,4 +126,19 @@ describe('/components/create_team/components/display_name', () => {
|
||||
|
||||
expect(screen.getByText('Please try another.', {exact: false})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should focus input when validation error occurs', () => {
|
||||
renderWithContext(
|
||||
<TeamUrl {...defaultProps}/>,
|
||||
);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -57,9 +57,11 @@ type Props = {
|
||||
}
|
||||
|
||||
export default class TeamUrl extends React.PureComponent<Props, State> {
|
||||
teamURLInput: React.RefObject<HTMLInputElement>;
|
||||
|
||||
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<Props, State> {
|
||||
defaultMessage='This field is required'
|
||||
/>),
|
||||
});
|
||||
this.teamURLInput.current?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,6 +112,7 @@ export default class TeamUrl extends React.PureComponent<Props, State> {
|
||||
}}
|
||||
/>),
|
||||
});
|
||||
this.teamURLInput.current?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -119,6 +123,7 @@ export default class TeamUrl extends React.PureComponent<Props, State> {
|
||||
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<Props, State> {
|
||||
let nameError = null;
|
||||
let nameDivClass = 'form-group';
|
||||
if (this.state.nameError) {
|
||||
nameError = <label className='control-label'>{this.state.nameError}</label>;
|
||||
nameError = (
|
||||
<label
|
||||
role='alert'
|
||||
className='control-label'
|
||||
id='teamURLInputError'
|
||||
>
|
||||
{this.state.nameError}
|
||||
</label>
|
||||
);
|
||||
nameDivClass += ' has-error';
|
||||
}
|
||||
|
||||
@@ -221,13 +234,13 @@ export default class TeamUrl extends React.PureComponent<Props, State> {
|
||||
className='signup-team-logo'
|
||||
src={logoImage}
|
||||
/>
|
||||
<h5>
|
||||
<label htmlFor='teamURLInput'>
|
||||
<FormattedMessage
|
||||
id='create_team.team_url.teamUrl'
|
||||
tagName='strong'
|
||||
defaultMessage='Team URL'
|
||||
/>
|
||||
</h5>
|
||||
</label>
|
||||
<div className={nameDivClass}>
|
||||
<div className='row'>
|
||||
<div className='col-sm-11'>
|
||||
@@ -242,6 +255,7 @@ export default class TeamUrl extends React.PureComponent<Props, State> {
|
||||
<input
|
||||
id='teamURLInput'
|
||||
type='text'
|
||||
ref={this.teamURLInput}
|
||||
className='form-control'
|
||||
placeholder=''
|
||||
maxLength={128}
|
||||
@@ -250,6 +264,7 @@ export default class TeamUrl extends React.PureComponent<Props, State> {
|
||||
onFocus={this.handleFocus}
|
||||
onChange={this.handleTeamURLInputChange}
|
||||
spellCheck='false'
|
||||
aria-describedby='teamURLInputError'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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<Props & RouteComponentProps, State> {
|
||||
export class CreateTeam extends React.PureComponent<Props & RouteComponentProps, State> {
|
||||
public constructor(props: Props & RouteComponentProps) {
|
||||
super(props);
|
||||
|
||||
@@ -65,6 +66,16 @@ export default class CreateTeam extends React.PureComponent<Props & RouteCompone
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
document.title = formatMessage({
|
||||
id: 'create_team.pageTitle',
|
||||
defaultMessage: 'Create a team - {siteName}',
|
||||
}, {
|
||||
siteName: this.props.siteName || 'Mattermost',
|
||||
});
|
||||
}
|
||||
|
||||
public updateParent = (state: State) => {
|
||||
this.setState(state);
|
||||
this.props.history.push('/create_team/' + state.wizard);
|
||||
@@ -156,3 +167,5 @@ export default class CreateTeam extends React.PureComponent<Props & RouteCompone
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(CreateTeam);
|
||||
|
||||
@@ -3647,6 +3647,7 @@
|
||||
"create_team.display_name.next": "Next",
|
||||
"create_team.display_name.required": "This field is required",
|
||||
"create_team.display_name.teamName": "Team Name",
|
||||
"create_team.pageTitle": "Create a team - {siteName}",
|
||||
"create_team.team_url.back": "Back to previous step",
|
||||
"create_team.team_url.charLength": "Name must be {min} or more characters up to a maximum of {max}",
|
||||
"create_team.team_url.creatingTeam": "Creating team...",
|
||||
|
||||
Ссылка в новой задаче
Block a user