]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18832 Add aria label on SCM account delete button in create user dialog
authorstanislavh <stanislav.honcharov@sonarsource.com>
Fri, 24 Mar 2023 11:35:34 +0000 (12:35 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 27 Mar 2023 20:03:02 +0000 (20:03 +0000)
server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts
server/sonar-web/src/main/js/app/styles/init/base.css
server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx
server/sonar-web/src/main/js/apps/users/components/UserForm.tsx
server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx
server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserForm-test.tsx.snap
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 0c8c719732ce2443910b9ce08195795cc171149b..c22c47bff132691464feee2543a44866147df688 100644 (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));
   }
index 7df1afe59fa89e9ebea3604185f5cc69bd004679..985e1baf7730a2467f9bc915726daeecd166715d 100644 (file)
@@ -102,10 +102,6 @@ button::-moz-focus-inner {
   padding: 0;
 }
 
-legend {
-  color: #000;
-}
-
 pre,
 code,
 kbd,
index e2ad60141ded65cf6a14c5ddd265aceb918310db..4661ee408db1ae703ff4cce3302db7cdd0ba35c7 100644 (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 () => {
index c45da28192abbe48b5ddb5bfaece378b408f6af7..03c9588fa106c0dea35f2411497d8b3a9daed023 100644 (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>
index 934157b3fa57fae5b77cd6146a3222ef60b7d7ff..2bbc3074b5260ee85decd33cd014df62d4d88dfe 100644 (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>
+  );
 }
index d081432b1790c2d5111703459b8ec905e99f2499..8c0d43a038c993e23f6cfbb78fc7109a10a9d6c0 100644 (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"
         >
index a12def9b4dc3855a7c78f6e7f1c34acda9b2f21c..62622ab265063299ef0b26052c4b3f155a7ddf3c 100644 (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