Browse Source

SONAR-18832 Add aria label on SCM account delete button in create user dialog

tags/10.0.0.68432
stanislavh 1 year ago
parent
commit
dc25d166d7

+ 41
- 13
server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts View File

@@ -23,27 +23,30 @@ import { mockClusterSysInfo, mockIdentityProvider, mockUser } from '../../helper
import { IdentityProvider, Paging, SysInfoCluster } from '../../types/types';
import { User } from '../../types/users';
import { getSystemInfo } from '../system';
import { getIdentityProviders, searchUsers } from '../users';
import { createUser, getIdentityProviders, searchUsers } from '../users';

const DEFAULT_USERS = [
mockUser({
managed: true,
login: 'bob.marley',
name: 'Bob Marley',
}),
mockUser({
managed: false,
login: 'alice.merveille',
name: 'Alice Merveille',
}),
];

export default class UsersServiceMock {
isManaged = true;
users = [
mockUser({
managed: true,
login: 'bob.marley',
name: 'Bob Marley',
}),
mockUser({
managed: false,
login: 'alice.merveille',
name: 'Alice Merveille',
}),
];
users = cloneDeep(DEFAULT_USERS);

constructor() {
jest.mocked(getSystemInfo).mockImplementation(this.handleGetSystemInfo);
jest.mocked(getIdentityProviders).mockImplementation(this.handleGetIdentityProviders);
jest.mocked(searchUsers).mockImplementation((p) => this.handleSearchUsers(p));
jest.mocked(createUser).mockImplementation(this.handleCreateUser);
}

setIsManaged(managed: boolean) {
@@ -67,6 +70,26 @@ export default class UsersServiceMock {
return this.reply({ paging, users: this.users });
};

handleCreateUser = (data: {
email?: string;
local?: boolean;
login: string;
name: string;
password?: string;
scmAccount: string[];
}) => {
const { email, local, login, name, scmAccount } = data;
const newUser = mockUser({
email,
local,
login,
name,
scmAccounts: scmAccount,
});
this.users.push(newUser);
return this.reply(undefined);
};

handleGetIdentityProviders = (): Promise<{ identityProviders: IdentityProvider[] }> => {
return this.reply({ identityProviders: [mockIdentityProvider()] });
};
@@ -87,6 +110,11 @@ export default class UsersServiceMock {
);
};

reset = () => {
this.isManaged = true;
this.users = cloneDeep(DEFAULT_USERS);
};

reply<T>(response: T): Promise<T> {
return Promise.resolve(cloneDeep(response));
}

+ 0
- 4
server/sonar-web/src/main/js/app/styles/init/base.css View File

@@ -102,10 +102,6 @@ button::-moz-focus-inner {
padding: 0;
}

legend {
color: #000;
}

pre,
code,
kbd,

+ 35
- 1
server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx View File

@@ -20,7 +20,7 @@

import userEvent from '@testing-library/user-event';
import * as React from 'react';
import { byRole, byText } from 'testing-library-selector';
import { byLabelText, byRole, byText } from 'testing-library-selector';
import UsersServiceMock from '../../../api/mocks/UsersServiceMock';
import { renderApp } from '../../../helpers/testReactTestingUtils';
import UsersApp from '../UsersApp';
@@ -47,8 +47,25 @@ const ui = {
bobUpdateGroupButton: byRole('button', { name: 'users.update_users_groups.bob.marley' }),
bobUpdateButton: byRole('button', { name: 'users.manage_user.bob.marley' }),
bobRow: byRole('row', { name: 'BM Bob Marley bob.marley never' }),
loginInput: byRole('textbox', { name: /login/ }),
userNameInput: byRole('textbox', { name: /name/ }),
passwordInput: byLabelText(/password/),
scmAddButton: byRole('button', { name: 'add_verb' }),
createUserDialogButton: byRole('button', { name: 'create' }),
dialogSCMInputs: byRole('textbox', { name: /users.create_user.scm_account/ }),
dialogSCMInput: (value?: string) =>
byRole('textbox', { name: `users.create_user.scm_account_${value ? `x.${value}` : 'new'}` }),
deleteSCMButton: (value?: string) =>
byRole('button', {
name: `remove_x.users.create_user.scm_account_${value ? `x.${value}` : 'new'}`,
}),
jackRow: byRole('row', { name: /Jack/ }),
};

afterAll(() => {
handler.reset();
});

describe('in non managed mode', () => {
beforeEach(() => {
handler.setIsManaged(false);
@@ -59,6 +76,23 @@ describe('in non managed mode', () => {

expect(await ui.description.find()).toBeInTheDocument();
expect(ui.createUserButton.get()).toBeEnabled();
await userEvent.click(ui.createUserButton.get());

await userEvent.type(ui.loginInput.get(), 'Login');
await userEvent.type(ui.userNameInput.get(), 'Jack');
await userEvent.type(ui.passwordInput.get(), 'Password');
// Add SCM account
expect(ui.dialogSCMInputs.queryAll()).toHaveLength(0);
await userEvent.click(ui.scmAddButton.get());
expect(ui.dialogSCMInputs.getAll()).toHaveLength(1);
await userEvent.type(ui.dialogSCMInput().get(), 'SCM');
expect(ui.dialogSCMInput('SCM').get()).toBeInTheDocument();
// Remove SCM account
await userEvent.click(ui.deleteSCMButton('SCM').get());
expect(ui.dialogSCMInputs.queryAll()).toHaveLength(0);

await userEvent.click(ui.createUserDialogButton.get());
expect(ui.jackRow.get()).toBeInTheDocument();
});

it("should be able to add/remove user's group", async () => {

+ 17
- 15
server/sonar-web/src/main/js/apps/users/components/UserForm.tsx View File

@@ -240,21 +240,23 @@ export default class UserForm extends React.PureComponent<Props, State> {
</div>
)}
<div className="modal-field">
<label>{translate('my_profile.scm_accounts')}</label>
{this.state.scmAccounts.map((scm, idx) => (
<UserScmAccountInput
idx={idx}
key={idx}
onChange={this.handleUpdateScmAccount}
onRemove={this.handleRemoveScmAccount}
scmAccount={scm}
/>
))}
<div className="spacer-bottom">
<Button className="js-scm-account-add" onClick={this.handleAddScmAccount}>
{translate('add_verb')}
</Button>
</div>
<fieldset>
<legend>{translate('my_profile.scm_accounts')}</legend>
{this.state.scmAccounts.map((scm, idx) => (
<UserScmAccountInput
idx={idx}
key={idx}
onChange={this.handleUpdateScmAccount}
onRemove={this.handleRemoveScmAccount}
scmAccount={scm}
/>
))}
<div className="spacer-bottom">
<Button className="js-scm-account-add" onClick={this.handleAddScmAccount}>
{translate('add_verb')}
</Button>
</div>
</fieldset>
<p className="note">{translate('user.login_or_email_used_as_scm_account')}</p>
</div>
</div>

+ 25
- 17
server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx View File

@@ -19,6 +19,7 @@
*/
import * as React from 'react';
import { DeleteButton } from '../../../components/controls/buttons';
import { translate, translateWithParameters } from '../../../helpers/l10n';

export interface Props {
idx: number;
@@ -27,23 +28,30 @@ export interface Props {
onRemove: (idx: number) => void;
}

export default class UserScmAccountInput extends React.PureComponent<Props> {
handleChange = (event: React.SyntheticEvent<HTMLInputElement>) =>
this.props.onChange(this.props.idx, event.currentTarget.value);
export default function UserScmAccountInput(props: Props) {
const { idx, scmAccount } = props;

handleRemove = () => this.props.onRemove(this.props.idx);
const inputAriaLabel = scmAccount.trim()
? translateWithParameters('users.create_user.scm_account_x', scmAccount)
: translate('users.create_user.scm_account_new');

render() {
return (
<div className="js-scm-account display-flex-row spacer-bottom">
<input
maxLength={255}
onChange={this.handleChange}
type="text"
value={this.props.scmAccount}
/>
<DeleteButton onClick={this.handleRemove} />
</div>
);
}
return (
<div className="js-scm-account display-flex-row spacer-bottom">
<input
maxLength={255}
onChange={(event) => {
props.onChange(idx, event.currentTarget.value);
}}
type="text"
aria-label={inputAriaLabel}
value={scmAccount}
/>
<DeleteButton
aria-label={translateWithParameters('remove_x', inputAriaLabel)}
onClick={() => {
props.onRemove(idx);
}}
/>
</div>
);
}

+ 28
- 24
server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserForm-test.tsx.snap View File

@@ -68,19 +68,21 @@ exports[`should render correctly 1`] = `
<div
className="modal-field"
>
<label>
my_profile.scm_accounts
</label>
<div
className="spacer-bottom"
>
<Button
className="js-scm-account-add"
onClick={[Function]}
<fieldset>
<legend>
my_profile.scm_accounts
</legend>
<div
className="spacer-bottom"
>
add_verb
</Button>
</div>
<Button
className="js-scm-account-add"
onClick={[Function]}
>
add_verb
</Button>
</div>
</fieldset>
<p
className="note"
>
@@ -218,19 +220,21 @@ exports[`should render correctly 2`] = `
<div
className="modal-field"
>
<label>
my_profile.scm_accounts
</label>
<div
className="spacer-bottom"
>
<Button
className="js-scm-account-add"
onClick={[Function]}
<fieldset>
<legend>
my_profile.scm_accounts
</legend>
<div
className="spacer-bottom"
>
add_verb
</Button>
</div>
<Button
className="js-scm-account-add"
onClick={[Function]}
>
add_verb
</Button>
</div>
</fieldset>
<p
className="note"
>

+ 3
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -165,7 +165,7 @@ recommended=Recommended
refresh=Refresh
reload=Reload
remove=Remove
remove_x=Remove {x}
remove_x=Remove {0}
rename=Rename
replaces=Replaces
reset_verb=Reset
@@ -4357,6 +4357,8 @@ users.delete_user.help=A user account cannot be reactivated once their personal
users.delete_user.help.link=Learn more
users.delete_user.documentation=Authentication
users.create_user=Create User
users.create_user.scm_account_new=New SCM account
users.create_user.scm_account_x=SCM account '{0}'
users.update_user=Update User
users.cannot_update_delegated_user=You cannot update the name and email of this user, as it is controlled by an external identity provider.
users.minimum_x_characters=Minimum {0} characters

Loading…
Cancel
Save