Skip to content
Merged
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 @@ -17,7 +17,7 @@ exports[`<AppLayout /> snapshots renders correctly with CMS: header background 1

exports[`<AppLayout /> snapshots renders correctly with CMS: header logo 1`] = `
<img
alt="custom-header-logo"
alt="CMS: custom-header-logo"
class="h-auto w-[140px] mx-0"
src="https://cdn.accounts.firefox.com/other/firefox-browser-logo.svg"
/>
Expand All @@ -29,7 +29,7 @@ exports[`<AppLayout /> snapshots renders correctly with CMS: split layout main 1
/>
`;

exports[`<AppLayout /> snapshots renders correctly with CMS: title 1`] = `"MOCK Shared Title | Mozilla accounts"`;
exports[`<AppLayout /> snapshots renders correctly with CMS: title 1`] = `"CMS: Shared Title | Mozilla accounts"`;

exports[`<AppLayout /> snapshots renders correctly without CMS: background wrapper 1`] = `
<div
Expand Down
10 changes: 5 additions & 5 deletions packages/fxa-settings/src/pages/Index/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,12 @@ describe('Index page', () => {
).toBeInTheDocument();

screen.getByRole('heading', {
name: 'Sign up or sign in to your Mozilla account',
name: MOCK_CMS_INFO.EmailFirstPage.headline,
});
screen.getByText(MOCK_CMS_INFO.EmailFirstPage.description);
screen.getByRole('button', {
name: MOCK_CMS_INFO.EmailFirstPage.primaryButtonText,
});
screen.getByText(
'Stay protected with continuous data monitoring and automatic data removal.'
);
screen.getByRole('button', { name: 'Continue' });
});

it('renders as expected with cms info on mobile', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { withLocalization } from 'fxa-react/lib/storybooks';
import SetPassword from '.';
import { Meta } from '@storybook/react';
import { SetPasswordProps } from './interfaces';
import { Subject } from './mocks';
import { createMockIntegrationWithCms } from '../../mocks';
import { createMockIntegration, Subject } from './mocks';
import { createMockIntegrationWithCms, MOCK_CMS_INFO } from '../../mocks';

export default {
title: 'Pages/PostVerify/SetPassword',
Expand All @@ -24,6 +24,18 @@ const storyWithProps = ({

export const Default = storyWithProps();

export const ThirdPartyAuthDefault = storyWithProps({
passwordCreationReason: 'third_party_auth',
});

export const OtpDefault = storyWithProps({
passwordCreationReason: 'otp',
});

export const PasskeyDefault = storyWithProps({
passwordCreationReason: 'passkey',
});

export const WithCms = storyWithProps({
integration: createMockIntegrationWithCms(),
});
Expand All @@ -32,3 +44,51 @@ export const OtpWithCms = storyWithProps({
passwordCreationReason: 'otp',
integration: createMockIntegrationWithCms(),
});

export const ThirdPartyAuthWithCms = storyWithProps({
passwordCreationReason: 'third_party_auth',
integration: createMockIntegrationWithCms(),
});

export const PasskeyWithCms = storyWithProps({
passwordCreationReason: 'third_party_auth',
integration: createMockIntegrationWithCms(),
});

const cmsIntegrationWithLegalTerms = createMockIntegration(
{
...MOCK_CMS_INFO,
PostVerifySetPasswordPage: {
...MOCK_CMS_INFO.SignupSetPasswordPage,
headline: 'VPN Headline (from cms)',
description: 'VPN description (from cms)',
},
},
{
legalTerms: {
label: 'Customized:',
termsOfServiceLink: 'https://www.mozilla.org/customized/terms/',
privacyNoticeLink: 'https://www.mozilla.org/customized/privacy/',
fontSize: 'default',
},
}
);

export const WithCmsAndLegalTerms = storyWithProps({
integration: cmsIntegrationWithLegalTerms,
});

export const OtpWithCmsAndLegalTerms = storyWithProps({
passwordCreationReason: 'otp',
integration: cmsIntegrationWithLegalTerms,
});

export const ThirdPartyWithAndCmsAndLegalTerms = storyWithProps({
passwordCreationReason: 'third_party_auth',
integration: cmsIntegrationWithLegalTerms,
});

export const PasskeyWithCmsAndLegalTerms = storyWithProps({
passwordCreationReason: 'passkey',
integration: cmsIntegrationWithLegalTerms,
});
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,51 @@ describe('SetPassword page', () => {
).toBeInTheDocument();
});

it('renders the description above the email to match the Signup layout', () => {
renderWithLocalizationProvider(<Subject />);

const description = screen.getByText(
'This encrypts your data. It needs to be different from your Google or Apple account password.'
);
const email = screen.getByText(MOCK_EMAIL);

// DOCUMENT_POSITION_FOLLOWING means `email` appears after `description`.
expect(
description.compareDocumentPosition(email) &
Node.DOCUMENT_POSITION_FOLLOWING
).toBeTruthy();
});

describe('terms and privacy agreement', () => {
it('renders the default Mozilla Terms of Service and Privacy Notice links when no custom legal terms are set', () => {
renderWithLocalizationProvider(<Subject />);

expect(
screen.getByRole('link', { name: 'Terms of Service' })
).toBeInTheDocument();
expect(
screen.getByRole('link', { name: 'Privacy Notice' })
).toBeInTheDocument();
});

it('renders the client-specific legal terms when the integration provides them', () => {
renderWithLocalizationProvider(
<Subject
integration={createMockIntegration(undefined, {
legalTerms: {
label: 'Mozilla VPN',
termsOfServiceLink: 'https://www.mozilla.org/vpn/terms/',
privacyNoticeLink: 'https://www.mozilla.org/vpn/privacy/',
fontSize: 'default',
},
})}
/>
);

expect(screen.getByText(/Mozilla VPN:/)).toBeInTheDocument();
});
});

describe('Glean events', () => {
// First row asserts the default-arg behaviour (no prop passed); the
// others assert explicit pass-through for each non-default value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getCmsHeadlineClassName,
getCmsHeadlineStyle,
} from '../../../components/CardHeader';
import TermsPrivacyAgreement from '../../../components/TermsPrivacyAgreement';
import GleanMetrics from '../../../lib/glean';

export const SetPassword = ({
Expand Down Expand Up @@ -118,8 +119,6 @@ export const SetPassword = ({
<h1 className="card-header">Create password to sync</h1>
</FtlMsg>
)}
<p className="break-all mt-2">{email}</p>

{bannerErrorText && (
<Banner type="error" content={{ localizedHeading: bannerErrorText }} />
)}
Expand All @@ -143,6 +142,8 @@ export const SetPassword = ({
</FtlMsg>
)}

<p className="break-all mb-5">{email}</p>

<FormSetupAccount
{...{
formState,
Expand All @@ -162,6 +163,8 @@ export const SetPassword = ({
passwordFormType="post-verify-set-password"
cmsButton={cmsButton}
/>

<TermsPrivacyAgreement legalTerms={integration.getLegalTerms?.()} />
</AppLayout>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type CreatePasswordHandler = (

export type PostVerifySetPasswordIntegration = Pick<
Integration,
'getCmsInfo' | 'isSync'
'getCmsInfo' | 'isSync' | 'getLegalTerms'
>;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ import {
import { MOCK_EMAIL } from '../../mocks';
import { mockUseFxAStatus } from '../../../lib/hooks/useFxAStatus/mocks';
import { RelierCmsInfo } from '../../../models';
import { RelierLegalTerms } from '../../../models/integrations/relier-interfaces';

export function createMockIntegration(
cmsInfo?: RelierCmsInfo,
{ isSync = true } = {}
{
isSync = true,
legalTerms,
}: { isSync?: boolean; legalTerms?: RelierLegalTerms } = {}
): PostVerifySetPasswordIntegration {
return {
getCmsInfo: () => cmsInfo,
isSync: () => isSync,
getLegalTerms: () => legalTerms,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

exports[`Signin component snapshots - CMS renders AppLayout with CMS header logo 1`] = `
<img
alt="custom-header-logo"
alt="CMS: custom-header-logo"
class="h-auto w-[140px] mx-0"
src="https://cdn.accounts.firefox.com/other/firefox-browser-logo.svg"
/>
Expand All @@ -12,15 +12,15 @@ exports[`Signin component snapshots - CMS renders CardHeader with CMS content wh
<p
class="card-subheader"
>
for your Mozilla account
CMS: for your Mozilla account
</p>
`;

exports[`Signin component snapshots - CMS renders CardHeader with CMS content when password is needed: cms headline 1`] = `
<h1
class="card-header-cms text-xl"
>
Enter your password
CMS: Enter your password
</h1>
`;

Expand All @@ -38,6 +38,6 @@ exports[`Signin component snapshots - CMS renders the CMS-styled submit button 1
style="--cta-bg: #6B4DFB; --cta-border: #6B4DFB; --cta-active: #6B4DFB; --cta-disabled: #6B4DFB; color: rgb(255, 255, 255);"
type="submit"
>
Sign in
CMS: Sign in
</button>
`;
4 changes: 3 additions & 1 deletion packages/fxa-settings/src/pages/Signin/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1788,7 +1788,9 @@ describe('Signin component', () => {
it('renders the CMS-styled submit button', () => {
render({ integration: createMockSigninWebIntegration(cmsProps) });

const submitButton = screen.getByRole('button', { name: 'Sign in' });
const submitButton = screen.getByRole('button', {
name: cmsProps.cmsInfo.SigninPage.primaryButtonText,
});

expect(submitButton).toMatchSnapshot();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,15 @@ describe('ConfirmSignupCode page', () => {
// testAllL10n(screen, bundle);

const headingEl = screen.getByRole('heading', { level: 1 });
expect(headingEl).toHaveTextContent('Enter confirmation code');
screen.getByText('For your Mozilla account');
expect(headingEl).toHaveTextContent(
MOCK_CMS_INFO.SignupConfirmCodePage.headline
);
screen.getByText(MOCK_CMS_INFO.SignupConfirmCodePage.description);
screen.getByLabelText('Enter 6-digit code');

screen.getByRole('button', { name: 'Confirm' });
screen.getByRole('button', {
name: MOCK_CMS_INFO.SignupConfirmCodePage.primaryButtonText,
});
screen.getByRole('button', { name: 'Email new code.' });
screen.getByTestId('cms-logo');
screen.getByText(MOCK_CMS_INFO.shared.additionalAccessibilityInfo);
Expand All @@ -263,7 +267,10 @@ describe('ConfirmSignupCode page', () => {
integration,
finishOAuthFlowHandler: jest.fn().mockReturnValueOnce(tryAgainError()),
});
submit();
submit(
MOCK_SIGNUP_CODE,
MOCK_CMS_INFO.SignupConfirmCodePage.primaryButtonText
);

await waitFor(() => {
screen.getByText(OAUTH_ERRORS.TRY_AGAIN.message);
Expand All @@ -276,7 +283,10 @@ describe('ConfirmSignupCode page', () => {
integration,
finishOAuthFlowHandler: jest.fn(),
});
submit();
submit(
MOCK_SIGNUP_CODE,
MOCK_CMS_INFO.SignupConfirmCodePage.primaryButtonText
);

await waitFor(() => {
expect(fxaOAuthLoginSpy).not.toHaveBeenCalled();
Expand Down Expand Up @@ -315,7 +325,10 @@ describe('ConfirmSignupCode page', () => {
integration,
origin: 'signup',
});
submit(MOCK_SIGNUP_CODE);
submit(
MOCK_SIGNUP_CODE,
MOCK_CMS_INFO.SignupConfirmCodePage.primaryButtonText
);

await waitFor(() => {
expect(ReactUtils.hardNavigate).toHaveBeenCalledWith(
Expand All @@ -333,7 +346,10 @@ describe('ConfirmSignupCode page', () => {
integration,
origin: 'signin',
});
submit(MOCK_SIGNUP_CODE);
submit(
MOCK_SIGNUP_CODE,
MOCK_CMS_INFO.SignupConfirmCodePage.primaryButtonText
);

await waitFor(() => {
expect(ReactUtils.hardNavigate).toHaveBeenCalledWith(
Expand Down
10 changes: 7 additions & 3 deletions packages/fxa-settings/src/pages/Signup/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,10 @@ describe('Signup page', () => {
screen.queryByRole('button', { name: /Continue with Apple/ })
).toBeInTheDocument();

screen.getByRole('heading', { name: 'Create a password' });
screen.getByText('to continue');
screen.getByRole('heading', {
name: MOCK_CMS_INFO.SignupSetPasswordPage.headline,
});
screen.getByText(MOCK_CMS_INFO.SignupSetPasswordPage.description);
});

it('renders as expected when cms enabled and on mobile', async () => {
Expand All @@ -328,7 +330,9 @@ describe('Signup page', () => {
);

// Await to let react-hook-form's internal state updates settle.
await screen.findByRole('heading', { name: 'Create a password' });
await screen.findByRole('heading', {
name: MOCK_CMS_INFO.SignupSetPasswordPage.headline,
});

expect(
screen.queryByAltText(MOCK_CMS_INFO.SignupSetPasswordPage.logoAltText)
Expand Down
Loading