[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"
|
className="signup-team-logo"
|
||||||
src="logo.png"
|
src="logo.png"
|
||||||
/>
|
/>
|
||||||
<h5>
|
<label
|
||||||
|
htmlFor="teamNameInput"
|
||||||
|
>
|
||||||
<MemoizedFormattedMessage
|
<MemoizedFormattedMessage
|
||||||
defaultMessage="Team Name"
|
defaultMessage="Team Name"
|
||||||
id="create_team.display_name.teamName"
|
id="create_team.display_name.teamName"
|
||||||
tagName="strong"
|
tagName="strong"
|
||||||
/>
|
/>
|
||||||
</h5>
|
</label>
|
||||||
<div
|
<div
|
||||||
className="form-group"
|
className="form-group"
|
||||||
>
|
>
|
||||||
@@ -25,6 +27,7 @@ exports[`/components/create_team/components/display_name should match snapshot 1
|
|||||||
className="col-sm-9"
|
className="col-sm-9"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
aria-describedby="teamNameInputError"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="form-control"
|
className="form-control"
|
||||||
id="teamNameInput"
|
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> {
|
export default class TeamSignupDisplayNamePage extends React.PureComponent<Props, State> {
|
||||||
|
teamNameInput: React.RefObject<HTMLInputElement>;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.teamNameInput = React.createRef();
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
teamDisplayName: this.props.state.team?.display_name || '',
|
teamDisplayName: this.props.state.team?.display_name || '',
|
||||||
@@ -59,6 +62,7 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent<Props
|
|||||||
defaultMessage='This field is required'
|
defaultMessage='This field is required'
|
||||||
/>),
|
/>),
|
||||||
});
|
});
|
||||||
|
this.teamNameInput.current?.focus();
|
||||||
return;
|
return;
|
||||||
} else if (displayName.length < Constants.MIN_TEAMNAME_LENGTH || displayName.length > Constants.MAX_TEAMNAME_LENGTH) {
|
} else if (displayName.length < Constants.MIN_TEAMNAME_LENGTH || displayName.length > Constants.MAX_TEAMNAME_LENGTH) {
|
||||||
this.setState({nameError: (
|
this.setState({nameError: (
|
||||||
@@ -71,6 +75,7 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent<Props
|
|||||||
}}
|
}}
|
||||||
/>),
|
/>),
|
||||||
});
|
});
|
||||||
|
this.teamNameInput.current?.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +99,15 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent<Props
|
|||||||
let nameError = null;
|
let nameError = null;
|
||||||
let nameDivClass = 'form-group';
|
let nameDivClass = 'form-group';
|
||||||
if (this.state.nameError) {
|
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';
|
nameDivClass += ' has-error';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,19 +119,20 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent<Props
|
|||||||
className='signup-team-logo'
|
className='signup-team-logo'
|
||||||
src={logoImage}
|
src={logoImage}
|
||||||
/>
|
/>
|
||||||
<h5>
|
<label htmlFor='teamNameInput'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='create_team.display_name.teamName'
|
id='create_team.display_name.teamName'
|
||||||
tagName='strong'
|
tagName='strong'
|
||||||
defaultMessage='Team Name'
|
defaultMessage='Team Name'
|
||||||
/>
|
/>
|
||||||
</h5>
|
</label>
|
||||||
<div className={nameDivClass}>
|
<div className={nameDivClass}>
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<div className='col-sm-9'>
|
<div className='col-sm-9'>
|
||||||
<input
|
<input
|
||||||
id='teamNameInput'
|
id='teamNameInput'
|
||||||
type='text'
|
type='text'
|
||||||
|
ref={this.teamNameInput}
|
||||||
className='form-control'
|
className='form-control'
|
||||||
placeholder=''
|
placeholder=''
|
||||||
maxLength={128}
|
maxLength={128}
|
||||||
@@ -127,6 +141,7 @@ export default class TeamSignupDisplayNamePage extends React.PureComponent<Props
|
|||||||
onFocus={this.handleFocus}
|
onFocus={this.handleFocus}
|
||||||
onChange={this.handleDisplayNameChange}
|
onChange={this.handleDisplayNameChange}
|
||||||
spellCheck='false'
|
spellCheck='false'
|
||||||
|
aria-describedby='teamNameInputError'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ exports[`/components/create_team/components/display_name should match snapshot 1
|
|||||||
className="signup-team-logo"
|
className="signup-team-logo"
|
||||||
src="logo.png"
|
src="logo.png"
|
||||||
/>
|
/>
|
||||||
<h5>
|
<label
|
||||||
|
htmlFor="teamURLInput"
|
||||||
|
>
|
||||||
<MemoizedFormattedMessage
|
<MemoizedFormattedMessage
|
||||||
defaultMessage="Team URL"
|
defaultMessage="Team URL"
|
||||||
id="create_team.team_url.teamUrl"
|
id="create_team.team_url.teamUrl"
|
||||||
tagName="strong"
|
tagName="strong"
|
||||||
/>
|
/>
|
||||||
</h5>
|
</label>
|
||||||
<div
|
<div
|
||||||
className="form-group"
|
className="form-group"
|
||||||
>
|
>
|
||||||
@@ -37,6 +39,7 @@ exports[`/components/create_team/components/display_name should match snapshot 1
|
|||||||
</span>
|
</span>
|
||||||
</WithTooltip>
|
</WithTooltip>
|
||||||
<input
|
<input
|
||||||
|
aria-describedby="teamURLInputError"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="form-control"
|
className="form-control"
|
||||||
id="teamURLInput"
|
id="teamURLInput"
|
||||||
|
|||||||
@@ -126,4 +126,19 @@ describe('/components/create_team/components/display_name', () => {
|
|||||||
|
|
||||||
expect(screen.getByText('Please try another.', {exact: false})).toBeInTheDocument();
|
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> {
|
export default class TeamUrl extends React.PureComponent<Props, State> {
|
||||||
|
teamURLInput: React.RefObject<HTMLInputElement>;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.teamURLInput = React.createRef();
|
||||||
this.state = {
|
this.state = {
|
||||||
nameError: '',
|
nameError: '',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
@@ -95,6 +97,7 @@ export default class TeamUrl extends React.PureComponent<Props, State> {
|
|||||||
defaultMessage='This field is required'
|
defaultMessage='This field is required'
|
||||||
/>),
|
/>),
|
||||||
});
|
});
|
||||||
|
this.teamURLInput.current?.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +112,7 @@ export default class TeamUrl extends React.PureComponent<Props, State> {
|
|||||||
}}
|
}}
|
||||||
/>),
|
/>),
|
||||||
});
|
});
|
||||||
|
this.teamURLInput.current?.focus();
|
||||||
return;
|
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."
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +196,15 @@ export default class TeamUrl extends React.PureComponent<Props, State> {
|
|||||||
let nameError = null;
|
let nameError = null;
|
||||||
let nameDivClass = 'form-group';
|
let nameDivClass = 'form-group';
|
||||||
if (this.state.nameError) {
|
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';
|
nameDivClass += ' has-error';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,13 +234,13 @@ export default class TeamUrl extends React.PureComponent<Props, State> {
|
|||||||
className='signup-team-logo'
|
className='signup-team-logo'
|
||||||
src={logoImage}
|
src={logoImage}
|
||||||
/>
|
/>
|
||||||
<h5>
|
<label htmlFor='teamURLInput'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='create_team.team_url.teamUrl'
|
id='create_team.team_url.teamUrl'
|
||||||
tagName='strong'
|
tagName='strong'
|
||||||
defaultMessage='Team URL'
|
defaultMessage='Team URL'
|
||||||
/>
|
/>
|
||||||
</h5>
|
</label>
|
||||||
<div className={nameDivClass}>
|
<div className={nameDivClass}>
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<div className='col-sm-11'>
|
<div className='col-sm-11'>
|
||||||
@@ -242,6 +255,7 @@ export default class TeamUrl extends React.PureComponent<Props, State> {
|
|||||||
<input
|
<input
|
||||||
id='teamURLInput'
|
id='teamURLInput'
|
||||||
type='text'
|
type='text'
|
||||||
|
ref={this.teamURLInput}
|
||||||
className='form-control'
|
className='form-control'
|
||||||
placeholder=''
|
placeholder=''
|
||||||
maxLength={128}
|
maxLength={128}
|
||||||
@@ -250,6 +264,7 @@ export default class TeamUrl extends React.PureComponent<Props, State> {
|
|||||||
onFocus={this.handleFocus}
|
onFocus={this.handleFocus}
|
||||||
onChange={this.handleTeamURLInputChange}
|
onChange={this.handleTeamURLInputChange}
|
||||||
spellCheck='false'
|
spellCheck='false'
|
||||||
|
aria-describedby='teamURLInputError'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {shallow} from 'enzyme';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import CreateTeam from './create_team';
|
import {CreateTeam} from './create_team';
|
||||||
|
|
||||||
describe('component/create_team', () => {
|
describe('component/create_team', () => {
|
||||||
const baseProps = {
|
const baseProps = {
|
||||||
@@ -25,6 +25,7 @@ describe('component/create_team', () => {
|
|||||||
},
|
},
|
||||||
history: jest.fn(),
|
history: jest.fn(),
|
||||||
location: jest.fn(),
|
location: jest.fn(),
|
||||||
|
intl: {formatMessage: jest.fn()},
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
test('should match snapshot default', () => {
|
test('should match snapshot default', () => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
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 {Route, Switch, Redirect} from 'react-router-dom';
|
||||||
import type {RouteComponentProps} from 'react-router-dom';
|
import type {RouteComponentProps} from 'react-router-dom';
|
||||||
|
|
||||||
@@ -48,6 +48,7 @@ export type Props = {
|
|||||||
isCloud: boolean;
|
isCloud: boolean;
|
||||||
isFreeTrial: boolean;
|
isFreeTrial: boolean;
|
||||||
usageDeltas: CloudUsage;
|
usageDeltas: CloudUsage;
|
||||||
|
intl: IntlShape;
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@@ -55,7 +56,7 @@ type State = {
|
|||||||
wizard: string;
|
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) {
|
public constructor(props: Props & RouteComponentProps) {
|
||||||
super(props);
|
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) => {
|
public updateParent = (state: State) => {
|
||||||
this.setState(state);
|
this.setState(state);
|
||||||
this.props.history.push('/create_team/' + state.wizard);
|
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.next": "Next",
|
||||||
"create_team.display_name.required": "This field is required",
|
"create_team.display_name.required": "This field is required",
|
||||||
"create_team.display_name.teamName": "Team Name",
|
"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.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.charLength": "Name must be {min} or more characters up to a maximum of {max}",
|
||||||
"create_team.team_url.creatingTeam": "Creating team...",
|
"create_team.team_url.creatingTeam": "Creating team...",
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user