diff --git a/packages/fxa-settings/src/pages/Pair2/Supplicant/TimeoutAndCancel/index.test.tsx b/packages/fxa-settings/src/pages/Pair2/Supplicant/TimeoutAndCancel/index.test.tsx
index 09bf107797f..8f04e490941 100644
--- a/packages/fxa-settings/src/pages/Pair2/Supplicant/TimeoutAndCancel/index.test.tsx
+++ b/packages/fxa-settings/src/pages/Pair2/Supplicant/TimeoutAndCancel/index.test.tsx
@@ -12,76 +12,110 @@ import { Subject } from './mocks';
const VARIANTS: Array<{
reason: PairingInterruptionReason;
heading: string;
+ descriptionFtlId: string;
description: string;
}> = [
{
reason: 'timeout',
heading: 'Looks like we timed out',
+ descriptionFtlId: 'pair2-supplicant-timeout-and-cancel-timeout-description',
description:
'To connect your mobile device and sync your Firefox data, visit firefox.com/pair on your computer.',
},
{
reason: 'canceled',
heading: 'Canceled',
+ descriptionFtlId:
+ 'pair2-supplicant-timeout-and-cancel-canceled-description',
description:
- 'To connect a device anytime, visit firefox.com/pair on your computer.',
+ 'To connect a device anytime, visit firefox.com/pair on your computer.',
},
];
+const getDescription = (ftlId: string) =>
+ screen.getAllByTestId('ftlmsg-mock').find((el) => el.id === ftlId)!;
+
describe('Pair2/Supplicant/TimeoutAndCancel page', () => {
- describe.each(VARIANTS)('$reason', ({ reason, heading, description }) => {
- // Guards against drift between the fallback text in the component and the
- // actual Fluent bundle, including a rename of either variant's ids.
- it.skip('renders every message with text matching the Fluent bundle', async () => {
- const bundle: FluentBundle = await getFtlBundle('settings');
- renderWithLocalizationProvider();
-
- const messages = screen
- .getAllByTestId('ftlmsg-mock')
- // The jest SVG stub renders the file name as the element's text, so
- // image messages can never match. Covered by
- // components/images/index.test.tsx.
- .filter((el) => !el.textContent?.endsWith('.svg'));
-
- expect(messages.length).toBeGreaterThan(0);
- messages.forEach((el) => testL10n(el, bundle));
- });
-
- it('renders the heading and description for this state', () => {
- renderWithLocalizationProvider();
-
- expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent(
- heading
- );
- screen.getByText(description);
- });
-
- it('exposes the brand lockup and illustration to assistive technology', () => {
- renderWithLocalizationProvider();
-
- expect(
- screen
- .getAllByRole('img')
- .map(
- (img) => img.getAttribute('alt') ?? img.getAttribute('aria-label')
- )
- ).toEqual([
- // AppLayout's page header, then the two images this card renders.
- 'Mozilla logo',
- 'Firefox logo',
- ]);
- });
-
- // Both states are dead ends by design — the user restarts from their
- // computer. Fail loudly if an unwired action is ever added here.
- it('renders no action', () => {
- renderWithLocalizationProvider();
-
- expect(screen.queryByRole('button')).not.toBeInTheDocument();
- // AppLayout's Mozilla logo is the only link on the page.
- expect(screen.getAllByRole('link')).toHaveLength(1);
- });
- });
+ describe.each(VARIANTS)(
+ '$reason',
+ ({ reason, heading, descriptionFtlId, description }) => {
+ // Guards against drift between the fallback text in the component and the
+ // actual Fluent bundle, including a rename of either variant's ids.
+ it('renders every message with text matching the Fluent bundle', async () => {
+ const bundle: FluentBundle = await getFtlBundle('settings');
+ renderWithLocalizationProvider();
+
+ const messages = screen
+ .getAllByTestId('ftlmsg-mock')
+ // The jest SVG stub renders the file name as the element's text, so
+ // image messages can never match. Covered by
+ // components/images/index.test.tsx.
+ .filter((el) => !el.textContent?.endsWith('.svg'))
+ // `testL10n` compares rendered text against the raw Fluent source, so
+ // the description's DOM overlay tag can never match it. It is checked
+ // tag-stripped below instead.
+ .filter((el) => el.id !== descriptionFtlId);
+
+ expect(messages.length).toBeGreaterThan(0);
+ messages.forEach((el) => testL10n(el, bundle));
+ });
+
+ it('keeps the description fallback text in step with the Fluent message', async () => {
+ const bundle: FluentBundle = await getFtlBundle('settings');
+ renderWithLocalizationProvider();
+
+ const message = bundle.getMessage(descriptionFtlId);
+ const source = bundle.formatPattern(message!.value!);
+
+ expect(getDescription(descriptionFtlId).textContent).toEqual(
+ source.replace(/<\/?b>/g, '')
+ );
+ });
+
+ it('renders the heading and description for this state', () => {
+ renderWithLocalizationProvider();
+
+ expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent(
+ heading
+ );
+ expect(getDescription(descriptionFtlId)).toHaveTextContent(description);
+ });
+
+ // The URL is a placeable inside the sentence, not a fragment glued on, so
+ // the emphasis has to come from an element the message can wrap.
+ it('emphasises the pairing URL with an element rather than literal tags', () => {
+ renderWithLocalizationProvider();
+
+ expect(screen.getByText('firefox.com/pair').tagName).toEqual('B');
+ });
+
+ it('exposes the brand lockup and illustration to assistive technology', () => {
+ renderWithLocalizationProvider();
+
+ expect(
+ screen
+ .getAllByRole('img')
+ .map(
+ (img) => img.getAttribute('alt') ?? img.getAttribute('aria-label')
+ )
+ ).toEqual([
+ // AppLayout's page header, then the two images this card renders.
+ 'Mozilla logo',
+ 'Firefox logo',
+ ]);
+ });
+
+ // Both states are dead ends by design — the user restarts from their
+ // computer. Fail loudly if an unwired action is ever added here.
+ it('renders no action', () => {
+ renderWithLocalizationProvider();
+
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
+ // AppLayout's Mozilla logo is the only link on the page.
+ expect(screen.getAllByRole('link')).toHaveLength(1);
+ });
+ }
+ );
it('shows different copy for each state', () => {
const { unmount } = renderWithLocalizationProvider(
diff --git a/packages/fxa-settings/src/pages/Pair2/Supplicant/TimeoutAndCancel/index.tsx b/packages/fxa-settings/src/pages/Pair2/Supplicant/TimeoutAndCancel/index.tsx
index c6e5083e32b..41035db3ab5 100644
--- a/packages/fxa-settings/src/pages/Pair2/Supplicant/TimeoutAndCancel/index.tsx
+++ b/packages/fxa-settings/src/pages/Pair2/Supplicant/TimeoutAndCancel/index.tsx
@@ -2,7 +2,7 @@
* 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 React, { ReactElement } from 'react';
import { FtlMsg } from 'fxa-react/lib/utils';
import AppLayout from '../../../../components/AppLayout';
import {
@@ -29,23 +29,36 @@ const COPY: Record<
headingFtlId: string;
heading: string;
descriptionFtlId: string;
- description: string;
+ /**
+ * Fallback markup rather than a string: both sentences emphasise the pairing
+ * URL through a `` the Fluent message wraps, so the fallback has to carry
+ * a real element instead of the tags as text.
+ */
+ description: ReactElement;
}
> = {
timeout: {
headingFtlId: 'pair2-supplicant-timeout-and-cancel-timeout-heading',
heading: 'Looks like we timed out',
descriptionFtlId: 'pair2-supplicant-timeout-and-cancel-timeout-description',
- description:
- 'To connect your mobile device and sync your Firefox data, visit firefox.com/pair on your computer.',
+ description: (
+ <>
+ To connect your mobile device and sync your Firefox data, visit{' '}
+ firefox.com/pair on your computer.
+ >
+ ),
},
canceled: {
headingFtlId: 'pair2-supplicant-timeout-and-cancel-canceled-heading',
heading: 'Canceled',
descriptionFtlId:
'pair2-supplicant-timeout-and-cancel-canceled-description',
- description:
- 'To connect a device anytime, visit firefox.com/pair on your computer.',
+ description: (
+ <>
+ To connect a device anytime, visit{' '}
+ firefox.com/pair on your computer.
+ >
+ ),
},
};
@@ -73,7 +86,10 @@ const TimeoutAndCancel = ({ reason }: TimeoutAndCancelProps) => {
{heading}
-
+ }}
+ >
{description}