Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

task(settings): Display password requirements inline #17605

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

// AUTOGENERATED BY glean_parser v14.5.2. DO NOT EDIT. DO NOT COMMIT.

import StringMetricType from '@mozilla/glean/private/metrics/string';
import BooleanMetricType from '@mozilla/glean/private/metrics/boolean';
import StringMetricType from '@mozilla/glean/private/metrics/string';

/**
* The name of the event
Expand Down
16 changes: 8 additions & 8 deletions packages/fxa-settings/src/components/CardHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ const CardHeader = (props: CardHeaderProps) => {

if (isDefaultService(props)) {
const spanElement: ReactElement = (
<span className="card-subheader">
<span className="card-subheader text-left">
to continue to {MozServices.Default}
</span>
);
Expand All @@ -153,7 +153,7 @@ const CardHeader = (props: CardHeaderProps) => {
id={props.headingWithDefaultServiceFtlId}
elems={{ span: spanElement }}
>
<h1 className="card-header">
<h1 className="card-header text-left">
{headingText} {spanElement}
</h1>
</FtlMsg>
Expand All @@ -171,7 +171,7 @@ const CardHeader = (props: CardHeaderProps) => {
vars={{ serviceName }}
elems={{ span: spanElement }}
>
<h1 className="card-header">
<h1 className="card-header text-left">
{headingText} {spanElement}
</h1>
</FtlMsg>
Expand Down Expand Up @@ -206,11 +206,11 @@ const CardHeader = (props: CardHeaderProps) => {

return (
<>
<h1 className="card-header">
<h1 className="card-header text-left">
<FtlMsg id={props.headingTextFtlId}>{headingText}</FtlMsg>
</h1>
<FtlMsg {...subheadingFtlMsgProps}>
<p className="card-subheader">
<p className="card-subheader text-left">
Continue to {logo ? logoElem : serviceName}
</p>
</FtlMsg>
Expand All @@ -225,7 +225,7 @@ const CardHeader = (props: CardHeaderProps) => {
);
return (
<FtlMsg id={headingAndSubheadingFtlId} elems={{ span: spanElement }}>
<h1 className="card-header">
<h1 className="card-header text-left">
{headingText} {spanElement}
</h1>
</FtlMsg>
Expand All @@ -238,7 +238,7 @@ const CardHeader = (props: CardHeaderProps) => {
);
return (
<FtlMsg id={props.headingAndSubheadingFtlId} elems={{ span: spanElem }}>
<h1 className="card-header">
<h1 className="card-header text-left">
{headingText} {spanElem}
</h1>
</FtlMsg>
Expand All @@ -247,7 +247,7 @@ const CardHeader = (props: CardHeaderProps) => {

return (
<FtlMsg id={props.headingTextFtlId}>
<h1 className="card-header mb-2">{headingText}</h1>
<h1 className="card-header text-left">{headingText}</h1>
</FtlMsg>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## FormPasswordInline
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be FormPasswordInlineCriteria?


form-password-with-inline-criteria-signup-new-password-label =
.label = Password
form-password-with-inline-criteria-signup-confirm-password-label =
.label = Repeat password
form-password-with-inline-criteria-signup-submit-button = Create account

form-password-with-inline-criteria-reset-new-password =
.label = New password
form-password-with-inline-criteria-confirm-password =
.label = Re-enter password
form-password-with-inline-criteria-reset-submit-button = Reset password

form-password-with-inline-criteria-match-error = Passwords do not match
form-password-with-inline-criteria-sr-too-short-message = Password must contain at least 8 characters.
form-password-with-inline-criteria-sr-not-email-message = Password must not contain your email address.
form-password-with-inline-criteria-sr-not-common-message = Password must not be a commonly used password.
form-password-with-inline-criteria-sr-requirements-met = The entered password respects all password requirements.
form-password-with-inline-criteria-sr-passwords-match = Entered passwords match.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import React from 'react';
import { Subject } from './mocks';
import AppLayout from '../AppLayout';
import FormPasswordInline from '.';
import { Meta } from '@storybook/react';
import { withLocalization } from 'fxa-react/lib/storybooks';

export default {
title: 'Components/FormPasswordWithInlineCriteria',
component: FormPasswordInline,
decorators: [withLocalization],
} as Meta;

export const ResetPassword = () => (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to show the error state of passwords not matching in storybook?

<AppLayout>
<div className="max-w-lg mx-auto">
<Subject passwordFormType="reset" />
</div>
</AppLayout>
);

export const Signup = () => (
<AppLayout>
<div className="max-w-lg mx-auto">
<Subject passwordFormType="signup" />
</div>
</AppLayout>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import React from 'react';
import { screen, fireEvent, waitFor, within } from '@testing-library/react';
import { UserEvent, userEvent } from '@testing-library/user-event';
import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider';
import { getFtlBundle, testAllL10n } from 'fxa-react/lib/test-utils';
import { FluentBundle } from '@fluent/bundle';
import { Subject } from './mocks';

describe('FormPasswordWithInlineCriteria component', () => {
let bundle: FluentBundle;
let user: UserEvent;

beforeEach(() => {
user = userEvent.setup();
});
beforeAll(async () => {
bundle = await getFtlBundle('settings');
});
it('renders as expected for the reset form type', async () => {
renderWithLocalizationProvider(<Subject passwordFormType="reset" />);
testAllL10n(screen, bundle);

await waitFor(() => {
screen.getByLabelText('New password');
});
screen.getByLabelText('Re-enter password');
screen.getByRole('button', { name: 'Reset password' });
});

it('renders as expected for the signup form type', async () => {
renderWithLocalizationProvider(<Subject passwordFormType="signup" />);

await waitFor(() => {
screen.getByLabelText('Password');
});
screen.getByLabelText('Repeat password');
screen.getByRole('button', { name: 'Create account' });
});

it('displays the Password Strength Criteria when the new password field is in focus', async () => {
renderWithLocalizationProvider(<Subject passwordFormType="reset" />);
const newPasswordField = screen.getByLabelText('New password');

fireEvent.focus(newPasswordField);

await waitFor(() => screen.getByText('At least 8 characters'));
await waitFor(() => screen.getByText('Not your email address'));
await waitFor(() => screen.getByText('Not a commonly used password'));
});

// TODO in FXA-7482, review our password requirements and best way to display them
it('disallows space-only passwords', async () => {
renderWithLocalizationProvider(<Subject passwordFormType="signup" />);
const passwordField = screen.getByLabelText('Password');
user.type(passwordField, ' ');

await waitFor(() => screen.getByText('At least 8 characters'));
expect(screen.queryAllByText('icon-check.svg')).toHaveLength(2);
const passwordMinCharRequirement = screen.getByTestId(
'password-min-char-req'
);
const imageElement = within(passwordMinCharRequirement).getByRole('img');
expect(imageElement).toHaveTextContent('icon-x.svg');
});

it('disallows common passwords', async () => {
renderWithLocalizationProvider(<Subject passwordFormType="signup" />);
const passwordField = screen.getByLabelText('Password');
user.type(passwordField, 'mozilla accounts');
await waitFor(() => screen.getByText('At least 8 characters'));
expect(screen.queryAllByText('icon-check.svg')).toHaveLength(2);

const passwordNotCommonRequirement = screen.getByTestId(
'password-not-common-req'
);
const imageElement = within(passwordNotCommonRequirement).getByRole('img');
expect(imageElement).toHaveTextContent('icon-x.svg');
});
});
Loading