@openmrs/esm-login-app 8.0.1-pre.3547 → 8.0.1-pre.3552

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -51,7 +51,10 @@ const Login: React.FC = () => {
51
51
  useEffect(() => {
52
52
  if (showPasswordOnSeparateScreen) {
53
53
  if (showPasswordField) {
54
- passwordInputRef.current?.focus();
54
+ // Only focus password input if it's empty (to preserve browser autofilled values)
55
+ if (!passwordInputRef.current?.value) {
56
+ passwordInputRef.current?.focus();
57
+ }
55
58
  } else {
56
59
  usernameInputRef.current?.focus();
57
60
  }
@@ -157,6 +160,8 @@ const Login: React.FC = () => {
157
160
  <TextInput
158
161
  id="username"
159
162
  type="text"
163
+ name="username"
164
+ autoComplete="username"
160
165
  labelText={t('username', 'Username')}
161
166
  value={username}
162
167
  onChange={changeUsername}
@@ -165,19 +170,25 @@ const Login: React.FC = () => {
165
170
  autoFocus
166
171
  />
167
172
  {showPasswordOnSeparateScreen ? (
168
- showPasswordField ? (
169
- <>
173
+ <>
174
+ {/* Password input is always in DOM for browser autofill support, but visually hidden until username step is complete */}
175
+ <div className={showPasswordField ? undefined : styles.hiddenPasswordField}>
170
176
  <PasswordInput
171
177
  id="password"
172
178
  labelText={t('password', 'Password')}
173
179
  name="password"
180
+ autoComplete="current-password"
174
181
  onChange={changePassword}
175
182
  ref={passwordInputRef}
176
- required
183
+ required={showPasswordField}
177
184
  value={password}
178
185
  showPasswordLabel={t('showPassword', 'Show password')}
179
186
  invalidText={t('validValueRequired', 'A valid value is required')}
187
+ aria-hidden={!showPasswordField}
188
+ tabIndex={showPasswordField ? 0 : -1}
180
189
  />
190
+ </div>
191
+ {showPasswordField ? (
181
192
  <Button
182
193
  type="submit"
183
194
  className={styles.continueButton}
@@ -191,24 +202,25 @@ const Login: React.FC = () => {
191
202
  t('login', 'Log in')
192
203
  )}
193
204
  </Button>
194
- </>
195
- ) : (
196
- <Button
197
- className={styles.continueButton}
198
- renderIcon={(props) => <ArrowRightIcon size={24} {...props} />}
199
- iconDescription="Continue to password"
200
- onClick={continueLogin}
201
- disabled={!isLoginEnabled}
202
- >
203
- {t('continue', 'Continue')}
204
- </Button>
205
- )
205
+ ) : (
206
+ <Button
207
+ className={styles.continueButton}
208
+ renderIcon={(props) => <ArrowRightIcon size={24} {...props} />}
209
+ iconDescription="Continue to password"
210
+ onClick={continueLogin}
211
+ disabled={!isLoginEnabled}
212
+ >
213
+ {t('continue', 'Continue')}
214
+ </Button>
215
+ )}
216
+ </>
206
217
  ) : (
207
218
  <>
208
219
  <PasswordInput
209
220
  id="password"
210
221
  labelText={t('password', 'Password')}
211
222
  name="password"
223
+ autoComplete="current-password"
212
224
  onChange={changePassword}
213
225
  ref={passwordInputRef}
214
226
  required
@@ -156,3 +156,14 @@
156
156
  bottom: 100%;
157
157
  left: 0;
158
158
  }
159
+
160
+ // Hidden password field for browser autofill support
161
+ // Uses visibility:hidden instead of display:none to keep the input in the DOM
162
+ // This allows browsers to autofill both username and password credentials
163
+ .hiddenPasswordField {
164
+ visibility: hidden;
165
+ height: 0;
166
+ overflow: hidden;
167
+ position: absolute;
168
+ pointer-events: none;
169
+ }
@@ -172,7 +172,7 @@ describe('Login', () => {
172
172
  expect(loginButton).toBeInTheDocument();
173
173
  });
174
174
 
175
- it('should not render the password field when the showPasswordOnSeparateScreen config is true (default)', async () => {
175
+ it('should render password field hidden but present for autofill when showPasswordOnSeparateScreen config is true (default)', async () => {
176
176
  mockUseConfig.mockReturnValue({
177
177
  ...mockConfig,
178
178
  });
@@ -187,12 +187,14 @@ describe('Login', () => {
187
187
 
188
188
  const usernameInput = screen.queryByRole('textbox', { name: /username/i });
189
189
  const continueButton = screen.queryByRole('button', { name: /Continue/i });
190
- const passwordInput = screen.queryByLabelText(/password/i);
190
+ const passwordInput = screen.queryByLabelText(/^password$/i);
191
191
  const loginButton = screen.queryByRole('button', { name: /log in/i });
192
192
 
193
193
  expect(usernameInput).toBeInTheDocument();
194
194
  expect(continueButton).toBeInTheDocument();
195
- expect(passwordInput).not.toBeInTheDocument();
195
+ expect(passwordInput).toBeInTheDocument();
196
+ expect(passwordInput).toHaveAttribute('aria-hidden', 'true');
197
+ expect(passwordInput).toHaveAttribute('tabIndex', '-1');
196
198
  expect(loginButton).not.toBeInTheDocument();
197
199
  });
198
200
 
@@ -283,4 +285,31 @@ describe('Login', () => {
283
285
 
284
286
  expect(usernameInput).toHaveFocus();
285
287
  });
288
+
289
+ it('should make password input visible and accessible after continuing from username step', async () => {
290
+ const user = userEvent.setup();
291
+ mockUseConfig.mockReturnValue({
292
+ ...mockConfig,
293
+ });
294
+
295
+ renderWithRouter(
296
+ Login,
297
+ {},
298
+ {
299
+ route: '/login',
300
+ },
301
+ );
302
+
303
+ const usernameInput = screen.getByRole('textbox', { name: /username/i });
304
+ const continueButton = screen.getByRole('button', { name: /Continue/i });
305
+
306
+ await user.type(usernameInput, 'testuser');
307
+ await user.click(continueButton);
308
+
309
+ const passwordInput = screen.getByLabelText(/^password$/i);
310
+
311
+ // Password should now be visible and accessible
312
+ expect(passwordInput).toHaveAttribute('aria-hidden', 'false');
313
+ expect(passwordInput).toHaveAttribute('tabIndex', '0');
314
+ });
286
315
  });