diff --git a/packages/react-core/src/components/LoginPage/LoginForm.tsx b/packages/react-core/src/components/LoginPage/LoginForm.tsx index bc938a4cfeb..b8dd8ed097f 100644 --- a/packages/react-core/src/components/LoginPage/LoginForm.tsx +++ b/packages/react-core/src/components/LoginPage/LoginForm.tsx @@ -24,6 +24,8 @@ export interface LoginFormProps extends Omit, ' usernameLabel?: string; /** Value for the username */ usernameValue?: string; + /** Autocomplete value for the username input field */ + usernameAutoComplete?: string; /** Function that handles the onChange event for the username */ onChangeUsername?: (event: React.FormEvent, value: string) => void; /** Flag indicating if the username is valid */ @@ -34,6 +36,8 @@ export interface LoginFormProps extends Omit, ' passwordLabel?: string; /** Value for the password */ passwordValue?: string; + /** Autocomplete value for the password input field */ + passwordAutoComplete?: string; /** Function that handles the onChange event for the password */ onChangePassword?: (event: React.FormEvent, value: string) => void; /** Flag indicating if the password is valid */ @@ -66,11 +70,13 @@ export const LoginForm: React.FunctionComponent = ({ helperTextIcon = null, usernameLabel = 'Username', usernameValue = '', + usernameAutoComplete, onChangeUsername = () => undefined as any, isValidUsername = true, isPasswordRequired = true, passwordLabel = 'Password', passwordValue = '', + passwordAutoComplete, onChangePassword = () => undefined as any, isShowPasswordEnabled = false, hidePasswordAriaLabel = 'Hide password', @@ -88,6 +94,7 @@ export const LoginForm: React.FunctionComponent = ({ const passwordInput = ( = ({ )} { const passwordField = screen.getByLabelText(/password/i); expect(passwordField).not.toBeRequired(); }); + + test('preserves form autocomplete without setting input autocomplete by default', () => { + render(); + + expect(screen.getByRole('form', { name: 'Login' })).toHaveAttribute('autocomplete', 'off'); + expect(screen.getByRole('textbox', { name: /username/i })).not.toHaveAttribute('autocomplete'); + expect(screen.getByLabelText(/password/i)).not.toHaveAttribute('autocomplete'); + }); + + test.each(['username', 'email', 'off'])('sets username autocomplete to %s', (usernameAutoComplete) => { + render(); + + expect(screen.getByRole('textbox', { name: /username/i })).toHaveAttribute('autocomplete', usernameAutoComplete); + expect(screen.getByLabelText(/password/i)).not.toHaveAttribute('autocomplete'); + }); + + test.each(['current-password', 'new-password', 'off'])('sets password autocomplete to %s', (passwordAutoComplete) => { + render(); + + expect(screen.getByLabelText(/password/i)).toHaveAttribute('autocomplete', passwordAutoComplete); + expect(screen.getByRole('textbox', { name: /username/i })).not.toHaveAttribute('autocomplete'); + }); + + test('sets input autocomplete independently of form autocomplete', () => { + render( + + ); + + expect(screen.getByRole('form', { name: 'Login' })).toHaveAttribute('autocomplete', 'off'); + expect(screen.getByRole('textbox', { name: /username/i })).toHaveAttribute('autocomplete', 'username'); + expect(screen.getByLabelText(/password/i)).toHaveAttribute('autocomplete', 'current-password'); + }); + + test('preserves password autocomplete when showing and hiding the password', async () => { + const user = userEvent.setup(); + render(); + + expect(screen.getByLabelText(/^password/i)).toHaveAttribute('type', 'password'); + expect(screen.getByLabelText(/^password/i)).toHaveAttribute('autocomplete', 'new-password'); + + await user.click(screen.getByRole('button', { name: 'Show password' })); + + expect(screen.getByLabelText(/^password/i)).toHaveAttribute('type', 'text'); + expect(screen.getByLabelText(/^password/i)).toHaveAttribute('autocomplete', 'new-password'); + + await user.click(screen.getByRole('button', { name: 'Hide password' })); + + expect(screen.getByLabelText(/^password/i)).toHaveAttribute('type', 'password'); + expect(screen.getByLabelText(/^password/i)).toHaveAttribute('autocomplete', 'new-password'); + }); }); diff --git a/packages/react-core/src/components/LoginPage/examples/LoginPage.md b/packages/react-core/src/components/LoginPage/examples/LoginPage.md index cfb23bb323a..b8072174ad9 100644 --- a/packages/react-core/src/components/LoginPage/examples/LoginPage.md +++ b/packages/react-core/src/components/LoginPage/examples/LoginPage.md @@ -33,6 +33,8 @@ import GitlabIcon from '@patternfly/react-icons/dist/esm/icons/gitlab-icon'; By default, a login page requires users to enter both a username and a password into their respective fields. The username must always be a required field, but you can make the password optional by passing the `isPasswordRequired` property to the ``. +Use `usernameAutoComplete` and `passwordAutoComplete` to set autocomplete values on the respective inputs. This example uses `"username"` and `"current-password"` for an existing account. Use `passwordAutoComplete="new-password"` when users are creating a password. Both properties are optional; when omitted, the inputs inherit the form's autocomplete setting. + This example uses `brandImgProps` to pass the brand image source, alt text, and an extra class, which will be preferred over `brandImgSrc` when both are provided. ```ts file='./LoginPageBasic.tsx' isFullscreen diff --git a/packages/react-core/src/components/LoginPage/examples/LoginPageBasic.tsx b/packages/react-core/src/components/LoginPage/examples/LoginPageBasic.tsx index f653747d1ca..0b59945a89c 100644 --- a/packages/react-core/src/components/LoginPage/examples/LoginPageBasic.tsx +++ b/packages/react-core/src/components/LoginPage/examples/LoginPageBasic.tsx @@ -97,10 +97,12 @@ export const SimpleLoginPage: React.FunctionComponent = () => { helperTextIcon={} usernameLabel="Username" usernameValue={username} + usernameAutoComplete="username" onChangeUsername={handleUsernameChange} isValidUsername={isValidUsername} passwordLabel="Password" passwordValue={password} + passwordAutoComplete="current-password" onChangePassword={handlePasswordChange} isValidPassword={isValidPassword} rememberMeLabel="Keep me logged in for 30 days."