From dc25d166d7be1b6ec38464bf4e61023f2367cedf Mon Sep 17 00:00:00 2001 From: stanislavh Date: Fri, 24 Mar 2023 12:35:34 +0100 Subject: [PATCH] SONAR-18832 Add aria label on SCM account delete button in create user dialog --- .../src/main/js/api/mocks/UsersServiceMock.ts | 54 ++++++++++++++----- .../src/main/js/app/styles/init/base.css | 4 -- .../js/apps/users/__tests__/UsersApp-it.tsx | 36 ++++++++++++- .../js/apps/users/components/UserForm.tsx | 32 +++++------ .../users/components/UserScmAccountInput.tsx | 42 +++++++++------ .../__snapshots__/UserForm-test.tsx.snap | 52 +++++++++--------- .../resources/org/sonar/l10n/core.properties | 4 +- 7 files changed, 149 insertions(+), 75 deletions(-) diff --git a/server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts index 0c8c719732c..c22c47bff13 100644 --- a/server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts @@ -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(response: T): Promise { return Promise.resolve(cloneDeep(response)); } diff --git a/server/sonar-web/src/main/js/app/styles/init/base.css b/server/sonar-web/src/main/js/app/styles/init/base.css index 7df1afe59fa..985e1baf773 100644 --- a/server/sonar-web/src/main/js/app/styles/init/base.css +++ b/server/sonar-web/src/main/js/app/styles/init/base.css @@ -102,10 +102,6 @@ button::-moz-focus-inner { padding: 0; } -legend { - color: #000; -} - pre, code, kbd, diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx index e2ad60141de..4661ee408db 100644 --- a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx @@ -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 () => { diff --git a/server/sonar-web/src/main/js/apps/users/components/UserForm.tsx b/server/sonar-web/src/main/js/apps/users/components/UserForm.tsx index c45da28192a..03c9588fa10 100644 --- a/server/sonar-web/src/main/js/apps/users/components/UserForm.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/UserForm.tsx @@ -240,21 +240,23 @@ export default class UserForm extends React.PureComponent { )}
- - {this.state.scmAccounts.map((scm, idx) => ( - - ))} -
- -
+
+ {translate('my_profile.scm_accounts')} + {this.state.scmAccounts.map((scm, idx) => ( + + ))} +
+ +
+

{translate('user.login_or_email_used_as_scm_account')}

diff --git a/server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx b/server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx index 934157b3fa5..2bbc3074b52 100644 --- a/server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx @@ -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 { - handleChange = (event: React.SyntheticEvent) => - 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 ( -
- - -
- ); - } + return ( +
+ { + props.onChange(idx, event.currentTarget.value); + }} + type="text" + aria-label={inputAriaLabel} + value={scmAccount} + /> + { + props.onRemove(idx); + }} + /> +
+ ); } diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserForm-test.tsx.snap index d081432b179..8c0d43a038c 100644 --- a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserForm-test.tsx.snap @@ -68,19 +68,21 @@ exports[`should render correctly 1`] = `
- -
- -
+ +
+

@@ -218,19 +220,21 @@ exports[`should render correctly 2`] = `

- -
- -
+ +
+

diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index a12def9b4dc..62622ab2650 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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 -- 2.39.5