From bcf6c76b781a799f5a668dd579e51ecd22153798 Mon Sep 17 00:00:00 2001 From: DBlatcher Date: Tue, 25 Aug 2026 10:01:48 +0100 Subject: [PATCH 01/11] replace the html preview component with an inline figure --- .../stand-frontend/components/HTMLPreview.tsx | 76 +++++++++++++------ 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/src/apps/frontend/src/features/stand-frontend/components/HTMLPreview.tsx b/src/apps/frontend/src/features/stand-frontend/components/HTMLPreview.tsx index 08aadb55..d8431dec 100644 --- a/src/apps/frontend/src/features/stand-frontend/components/HTMLPreview.tsx +++ b/src/apps/frontend/src/features/stand-frontend/components/HTMLPreview.tsx @@ -1,22 +1,24 @@ -import { css } from '@emotion/react'; -import { HtmlPreview } from '@guardian/stand/HtmlPreviewLoader'; +import { + baseColors, + semanticColors, + semanticSizing, + semanticSpacing, +} from '@guardian/stand'; import { Typography } from '@guardian/stand/Typography'; import { useCallback, useContext, useEffect, useState } from 'react'; import { useWatch } from 'react-hook-form'; import type { NewsletterFormValues } from '../notification-forms'; import { NotificationFormContext } from '../NotificationContext'; import { kickerNameMap } from '../option-values'; +import { LoadingSpinner } from './LoadingSpinner'; // TO DO - this function will work with the current format of the notifcation emails // but we shoudl modidify the template used in email-rendering to include attributes // to more robustly identify the elements to update const modifyContent = ( - emailHtml: string, + body: HTMLElement, parameters: Partial, -): string => { - const body = document.createElement('body'); - body.innerHTML = emailHtml; - +) => { const { subject, kicker, preview } = parameters; const headlineElement = body.querySelector('h2'); const kickerElement = @@ -38,8 +40,6 @@ const modifyContent = ( Array.from(body.querySelectorAll('a')).forEach((link) => link.removeAttribute('href'), ); - - return body.innerHTML; }; export const HTMLPreview = () => { @@ -49,6 +49,10 @@ export const HTMLPreview = () => { } = useContext(NotificationFormContext); const parameters = useWatch(); const [emailHtml, setEmailHtml] = useState(); + + const [previewContainerElement, setPreviewContainerElement] = + useState(null); + const [errorMessage, setErrorMessage] = useState(); const [isLoading, setIsLoading] = useState(false); const stringifiedAudience = (parameters.audienceSegments ?? []).join(); @@ -73,9 +77,30 @@ export const HTMLPreview = () => { if (!result.success) { throw result.failure; } + return result.data.html; }, [webUrl, requestEmailHtml, stringifiedAudience]); + useEffect(() => { + if (!previewContainerElement || !emailHtml) { + return; + } + + const articleElement = previewContainerElement.querySelector('article'); + if (!articleElement) { + return; + } + articleElement.innerHTML = emailHtml; + }, [previewContainerElement, emailHtml]); + + useEffect(() => { + if (!previewContainerElement) { + return; + } + + modifyContent(previewContainerElement, parameters); + }, [parameters, previewContainerElement]); + useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect -- ok setIsLoading(true); @@ -90,20 +115,25 @@ export const HTMLPreview = () => { }, [fetchHtml]); return ( - no article html ` - } - errorMessage={errorMessage} - isLoading={isLoading} - title={ +
+
Newsletter email preview - } - widthOptions={[]} - defaultWidth={400} - cssOverrides={css({ width: '440px' })} - /> +
+ {!emailHtml &&
no article html
} + {errorMessage &&
{errorMessage}
} + {isLoading && } +
+
+
+
); }; From 71a410b798416dfbd321a53b7093c2ef4c70973d Mon Sep 17 00:00:00 2001 From: DBlatcher Date: Thu, 27 Aug 2026 15:18:58 +0100 Subject: [PATCH 02/11] do not modify html preview to include kicker --- .../features/stand-frontend/components/HTMLPreview.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/apps/frontend/src/features/stand-frontend/components/HTMLPreview.tsx b/src/apps/frontend/src/features/stand-frontend/components/HTMLPreview.tsx index d8431dec..cc2a794e 100644 --- a/src/apps/frontend/src/features/stand-frontend/components/HTMLPreview.tsx +++ b/src/apps/frontend/src/features/stand-frontend/components/HTMLPreview.tsx @@ -9,7 +9,6 @@ import { useCallback, useContext, useEffect, useState } from 'react'; import { useWatch } from 'react-hook-form'; import type { NewsletterFormValues } from '../notification-forms'; import { NotificationFormContext } from '../NotificationContext'; -import { kickerNameMap } from '../option-values'; import { LoadingSpinner } from './LoadingSpinner'; // TO DO - this function will work with the current format of the notifcation emails @@ -19,21 +18,14 @@ const modifyContent = ( body: HTMLElement, parameters: Partial, ) => { - const { subject, kicker, preview } = parameters; + const { subject, preview } = parameters; const headlineElement = body.querySelector('h2'); - const kickerElement = - headlineElement?.parentElement?.querySelector( - 'div:first-child', - ); const previewElement = headlineElement?.parentElement?.querySelector('h2~div'); if (subject && headlineElement) { headlineElement.innerText = subject; } - if (kicker && kickerElement) { - kickerElement.innerText = kickerNameMap[kicker]; - } if (preview && previewElement) { previewElement.innerText = preview; } From dc25c87559206b47290cc53b10601817b118b690 Mon Sep 17 00:00:00 2001 From: DBlatcher Date: Tue, 15 Sep 2026 12:12:31 +0100 Subject: [PATCH 03/11] do not display info messages or placeholder as if it were returned content, modify preview with `showPreview` parameter --- src/apps/frontend/src/preview/HTMLPreview.tsx | 109 +++++++++++++----- 1 file changed, 80 insertions(+), 29 deletions(-) diff --git a/src/apps/frontend/src/preview/HTMLPreview.tsx b/src/apps/frontend/src/preview/HTMLPreview.tsx index 81c5277a..e8b42511 100644 --- a/src/apps/frontend/src/preview/HTMLPreview.tsx +++ b/src/apps/frontend/src/preview/HTMLPreview.tsx @@ -1,9 +1,11 @@ +import { css } from '@emotion/react'; import { baseColors, semanticColors, semanticSizing, semanticSpacing, } from '@guardian/stand'; +import { InlineMessage } from '@guardian/stand/InlineMessage'; import { Typography } from '@guardian/stand/Typography'; import { useCallback, useContext, useEffect, useState } from 'react'; import { useWatch } from 'react-hook-form'; @@ -18,7 +20,7 @@ const modifyContent = ( body: HTMLElement, parameters: Partial, ) => { - const { subject, preview } = parameters; + const { subject, preview, showPreview } = parameters; const headlineElement = body.querySelector('h2'); const previewElement = headlineElement?.parentElement?.querySelector('h2~div'); @@ -26,33 +28,68 @@ const modifyContent = ( if (subject && headlineElement) { headlineElement.innerText = subject; } - if (preview && previewElement) { - previewElement.innerText = preview; + if (previewElement) { + previewElement.innerText = showPreview && preview ? preview : ''; } Array.from(body.querySelectorAll('a')).forEach((link) => link.removeAttribute('href'), ); }; +type PreviewData = { + html?: string | undefined; + error?: string | undefined; + info?: string | undefined; +}; + +const styles = { + previewFrame: css({ + maxWidth: 440, + borderWidth: semanticSizing.border.default, + borderColor: semanticColors.border.strong, + borderStyle: 'solid', + backgroundColor: baseColors.neutral[900], + position: 'relative', + }), + placeHolder: css({ + minHeight: 300, + backgroundColor: baseColors.neutral[700], + color: baseColors.neutral[0], + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }), + spinnerContainer: css({ + position: 'absolute', + inset: 0, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + backdropFilter: 'blur(2px)', + }), +}; + export const HTMLPreview = () => { const { notification: { content }, requestEmailHtml, } = useContext(NotificationFormContext); const parameters = useWatch(); - const [emailHtml, setEmailHtml] = useState(); - const [previewContainerElement, setPreviewContainerElement] = useState(null); + const [emailHtml, setEmailHtml] = useState(); const [errorMessage, setErrorMessage] = useState(); + const [infoMessage, setInfoMessage] = useState(); const [isLoading, setIsLoading] = useState(false); const stringifiedAudience = (parameters.audienceSegments ?? []).join(); const { webUrl } = content ?? {}; - const fetchHtml = useCallback(async () => { + const getPreview = useCallback(async (): Promise => { if (!webUrl) { - return `
No article loaded
`; + return { + info: 'No article loaded', + }; } const audience = stringifiedAudience .split(',') @@ -60,21 +97,27 @@ export const HTMLPreview = () => { .filter((item) => item.length > 0); if (audience.length === 0) { - return `
Choose an audience in order to preview the newsletter email
`; + return { + info: 'Choose an audience in order to preview the newsletter email', + }; } const result = await requestEmailHtml({ article: webUrl, audience: audience, }); + if (!result.success) { - throw result.failure; + return { + error: result.failure.message, + }; } - - return result.data.html; + return { + html: result.data.html, + }; }, [webUrl, requestEmailHtml, stringifiedAudience]); useEffect(() => { - if (!previewContainerElement || !emailHtml) { + if (!previewContainerElement) { return; } @@ -82,7 +125,7 @@ export const HTMLPreview = () => { if (!articleElement) { return; } - articleElement.innerHTML = emailHtml; + articleElement.innerHTML = emailHtml ?? ''; }, [previewContainerElement, emailHtml]); useEffect(() => { @@ -97,34 +140,42 @@ export const HTMLPreview = () => { // eslint-disable-next-line react-hooks/set-state-in-effect -- ok setIsLoading(true); setErrorMessage(undefined); - fetchHtml() - .then(setEmailHtml) + getPreview() + .then((result) => { + setEmailHtml(result.html); + setErrorMessage(result.error); + setInfoMessage(result.info); + }) .catch((err) => { console.error(err); setErrorMessage('failed to load'); }) .finally(() => setIsLoading(false)); - }, [fetchHtml]); + }, [getPreview]); return (
Newsletter email preview
- {!emailHtml &&
no article html
} - {errorMessage &&
{errorMessage}
} - {isLoading && } -
+ + {errorMessage && ( + {errorMessage} + )} + {infoMessage && ( + {infoMessage} + )} + +
+ {!emailHtml && ( +
Generated newsletter email preview
+ )} + {isLoading && ( +
+ +
+ )}
); From cf17856623f70c5222137c0137a76c08286114aa Mon Sep 17 00:00:00 2001 From: DBlatcher Date: Tue, 15 Sep 2026 12:23:09 +0100 Subject: [PATCH 04/11] rename component --- src/apps/frontend/src/preview/EmailPreviewSection.tsx | 4 ++-- .../preview/{HTMLPreview.tsx => NewsletterEmailPreview.tsx} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/apps/frontend/src/preview/{HTMLPreview.tsx => NewsletterEmailPreview.tsx} (99%) diff --git a/src/apps/frontend/src/preview/EmailPreviewSection.tsx b/src/apps/frontend/src/preview/EmailPreviewSection.tsx index 45d5b366..09724b4e 100644 --- a/src/apps/frontend/src/preview/EmailPreviewSection.tsx +++ b/src/apps/frontend/src/preview/EmailPreviewSection.tsx @@ -12,7 +12,7 @@ import { defaultNewsletterFormValues, type NewsletterFormValues, } from '../utils/notification-forms'; -import { HTMLPreview } from './HTMLPreview'; +import { NewsletterEmailPreview } from './NewsletterEmailPreview'; import { PreviewSection } from './PreviewSection'; export const EmailPreviewSection = () => { @@ -55,7 +55,7 @@ export const EmailPreviewSection = () => { Email appearance may vary across different email clients and devices - + ); diff --git a/src/apps/frontend/src/preview/HTMLPreview.tsx b/src/apps/frontend/src/preview/NewsletterEmailPreview.tsx similarity index 99% rename from src/apps/frontend/src/preview/HTMLPreview.tsx rename to src/apps/frontend/src/preview/NewsletterEmailPreview.tsx index e8b42511..3b843d74 100644 --- a/src/apps/frontend/src/preview/HTMLPreview.tsx +++ b/src/apps/frontend/src/preview/NewsletterEmailPreview.tsx @@ -69,7 +69,7 @@ const styles = { }), }; -export const HTMLPreview = () => { +export const NewsletterEmailPreview = () => { const { notification: { content }, requestEmailHtml, From 556dfa0444a2adbe88547c9059cdcc98b4639865 Mon Sep 17 00:00:00 2001 From: DBlatcher Date: Tue, 15 Sep 2026 13:01:49 +0100 Subject: [PATCH 05/11] add stories --- .../NewsletterEmailPreview.stories.tsx | 121 ++++++++++++++++++ .../frontend/src/testing/mock-fetch-email.ts | 11 ++ 2 files changed, 132 insertions(+) create mode 100644 src/apps/frontend/src/preview/NewsletterEmailPreview.stories.tsx diff --git a/src/apps/frontend/src/preview/NewsletterEmailPreview.stories.tsx b/src/apps/frontend/src/preview/NewsletterEmailPreview.stories.tsx new file mode 100644 index 00000000..eb158d2a --- /dev/null +++ b/src/apps/frontend/src/preview/NewsletterEmailPreview.stories.tsx @@ -0,0 +1,121 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { expect, waitFor, within } from 'storybook/test'; +import type { NotificationFormContextProps } from '../compose/NotificationContext'; +import { fetchFailError } from '../testing/api-fixtures'; +import { articleFixture } from '../testing/capi-fixtures'; +import { mockRequestEmailHtmlWithoutDelay } from '../testing/mock-fetch-email'; +import { WithNotificationContext } from '../testing/story-helpers'; +import type { NotificationState } from '../types'; +import type { NewsletterFormValues } from '../utils/notification-forms'; +import { defaultState } from '../utils/notification-reducer'; +import { NewsletterEmailPreview } from './NewsletterEmailPreview'; + +type StoryArgs = { + notificationState: NotificationState; + formValues?: Partial; + functions?: Partial< + Omit< + NotificationFormContextProps, + 'channel' | 'notification' | 'updateNotification' + > + >; +}; + +type Story = StoryObj; + +const meta: Meta = { + title: 'Dispatch/Preview/NewsletterEmailPreview', + component: NewsletterEmailPreview, + args: { + notificationState: defaultState, + }, + render: ({ notificationState, formValues, functions }) => + WithNotificationContext( + , + notificationState, + functions, + 'email', + formValues, + ), +}; + +export default meta; + +export const Default: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await expect(canvas.getByText('No article loaded')).toBeInTheDocument(); + }, +}; + +export const WithContentNoAudience: Story = { + args: { + notificationState: { + ...defaultState, + content: articleFixture, + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await expect( + canvas.getByText( + 'Choose an audience in order to preview the newsletter email', + ), + ).toBeInTheDocument(); + }, +}; + +export const WithContentAndAudience: Story = { + args: { + notificationState: { + ...defaultState, + content: articleFixture, + }, + formValues: { + audienceSegments: ['UK'], + }, + functions: { + requestEmailHtml: mockRequestEmailHtmlWithoutDelay, + }, + }, + play: async ({ canvasElement }) => { + await waitFor(() => + expect( + canvasElement.querySelector('article table h2'), + ).toBeInTheDocument(), + ); + }, +}; + +export const Loading: Story = { + args: { + notificationState: { + ...defaultState, + content: articleFixture, + }, + formValues: { + audienceSegments: ['UK'], + }, + functions: { + requestEmailHtml: () => new Promise(() => {}), + }, + }, +}; +export const FailedToLoad: Story = { + args: { + notificationState: { + ...defaultState, + content: articleFixture, + }, + formValues: { + audienceSegments: ['UK'], + }, + functions: { + requestEmailHtml: () => + Promise.resolve({ + success: false, + failure: fetchFailError, + }), + }, + }, +}; diff --git a/src/apps/frontend/src/testing/mock-fetch-email.ts b/src/apps/frontend/src/testing/mock-fetch-email.ts index ff10e6f0..7dbab775 100644 --- a/src/apps/frontend/src/testing/mock-fetch-email.ts +++ b/src/apps/frontend/src/testing/mock-fetch-email.ts @@ -59,3 +59,14 @@ export const mockRequestEmailHtml: RequestEmailHtml = (request) => { }, 500); }); }; + +export const mockRequestEmailHtmlWithoutDelay: RequestEmailHtml = (request) => + Promise.resolve({ + success: true, + data: { + html: buildHtml(request.audience), + articleId: + 'technology/2026/jul/28/apple-second-ever-5tn-company-as-investors-flee-ai-stocks', + newsletterId: 'some-newsletter', + }, + }); From c523ee2461555e998cb417044460df18e14fc066 Mon Sep 17 00:00:00 2001 From: DBlatcher Date: Tue, 15 Sep 2026 15:49:14 +0100 Subject: [PATCH 06/11] modify content on initial load, simplify state --- .../src/preview/NewsletterEmailPreview.tsx | 84 +++++++++---------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/src/apps/frontend/src/preview/NewsletterEmailPreview.tsx b/src/apps/frontend/src/preview/NewsletterEmailPreview.tsx index 3b843d74..45fbcea9 100644 --- a/src/apps/frontend/src/preview/NewsletterEmailPreview.tsx +++ b/src/apps/frontend/src/preview/NewsletterEmailPreview.tsx @@ -74,18 +74,35 @@ export const NewsletterEmailPreview = () => { notification: { content }, requestEmailHtml, } = useContext(NotificationFormContext); + const { webUrl } = content ?? {}; const parameters = useWatch(); + const stringifiedAudience = (parameters.audienceSegments ?? []).join(); + const [previewContainerElement, setPreviewContainerElement] = useState(null); - - const [emailHtml, setEmailHtml] = useState(); - const [errorMessage, setErrorMessage] = useState(); - const [infoMessage, setInfoMessage] = useState(); + const [preview, setPreview] = useState(); const [isLoading, setIsLoading] = useState(false); - const stringifiedAudience = (parameters.audienceSegments ?? []).join(); - const { webUrl } = content ?? {}; - const getPreview = useCallback(async (): Promise => { + useEffect(() => { + const articleElement = previewContainerElement?.querySelector('article'); + if (!articleElement) { + return; + } + articleElement.innerHTML = preview?.html ?? ''; + if (preview?.html) { + modifyContent(articleElement, parameters); + } + }, [previewContainerElement, preview, parameters]); + + useEffect(() => { + const articleElement = previewContainerElement?.querySelector('article'); + if (!articleElement) { + return; + } + modifyContent(articleElement, parameters); + }, [parameters, previewContainerElement]); + + const getPreviewData = useCallback(async (): Promise => { if (!webUrl) { return { info: 'No article loaded', @@ -116,42 +133,19 @@ export const NewsletterEmailPreview = () => { }; }, [webUrl, requestEmailHtml, stringifiedAudience]); - useEffect(() => { - if (!previewContainerElement) { - return; - } - - const articleElement = previewContainerElement.querySelector('article'); - if (!articleElement) { - return; - } - articleElement.innerHTML = emailHtml ?? ''; - }, [previewContainerElement, emailHtml]); - - useEffect(() => { - if (!previewContainerElement) { - return; - } - - modifyContent(previewContainerElement, parameters); - }, [parameters, previewContainerElement]); - useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect -- ok setIsLoading(true); - setErrorMessage(undefined); - getPreview() - .then((result) => { - setEmailHtml(result.html); - setErrorMessage(result.error); - setInfoMessage(result.info); - }) - .catch((err) => { - console.error(err); - setErrorMessage('failed to load'); - }) - .finally(() => setIsLoading(false)); - }, [getPreview]); + setPreview((preview) => ({ + errorMessage: undefined, + info: undefined, + html: preview?.html, + })); + void getPreviewData().then((result) => { + setPreview(result); + setIsLoading(false); + }); + }, [getPreviewData]); return (
@@ -159,16 +153,16 @@ export const NewsletterEmailPreview = () => { Newsletter email preview - {errorMessage && ( - {errorMessage} + {preview?.error && ( + {preview.error} )} - {infoMessage && ( - {infoMessage} + {preview?.info && ( + {preview.info} )}
- {!emailHtml && ( + {!preview?.html && (
Generated newsletter email preview
)} {isLoading && ( From 81d6c2c5864c1567697f989a00947cff85b59357 Mon Sep 17 00:00:00 2001 From: DBlatcher Date: Thu, 17 Sep 2026 09:05:36 +0100 Subject: [PATCH 07/11] undo the rename to manage merging --- src/apps/frontend/src/preview/EmailPreviewSection.tsx | 2 +- .../src/preview/{NewsletterEmailPreview.tsx => HTMLPreview.tsx} | 0 .../frontend/src/preview/NewsletterEmailPreview.stories.tsx | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/apps/frontend/src/preview/{NewsletterEmailPreview.tsx => HTMLPreview.tsx} (100%) diff --git a/src/apps/frontend/src/preview/EmailPreviewSection.tsx b/src/apps/frontend/src/preview/EmailPreviewSection.tsx index 09724b4e..9eecb9c2 100644 --- a/src/apps/frontend/src/preview/EmailPreviewSection.tsx +++ b/src/apps/frontend/src/preview/EmailPreviewSection.tsx @@ -12,7 +12,7 @@ import { defaultNewsletterFormValues, type NewsletterFormValues, } from '../utils/notification-forms'; -import { NewsletterEmailPreview } from './NewsletterEmailPreview'; +import { NewsletterEmailPreview } from './HTMLPreview'; import { PreviewSection } from './PreviewSection'; export const EmailPreviewSection = () => { diff --git a/src/apps/frontend/src/preview/NewsletterEmailPreview.tsx b/src/apps/frontend/src/preview/HTMLPreview.tsx similarity index 100% rename from src/apps/frontend/src/preview/NewsletterEmailPreview.tsx rename to src/apps/frontend/src/preview/HTMLPreview.tsx diff --git a/src/apps/frontend/src/preview/NewsletterEmailPreview.stories.tsx b/src/apps/frontend/src/preview/NewsletterEmailPreview.stories.tsx index eb158d2a..c6b17665 100644 --- a/src/apps/frontend/src/preview/NewsletterEmailPreview.stories.tsx +++ b/src/apps/frontend/src/preview/NewsletterEmailPreview.stories.tsx @@ -8,7 +8,7 @@ import { WithNotificationContext } from '../testing/story-helpers'; import type { NotificationState } from '../types'; import type { NewsletterFormValues } from '../utils/notification-forms'; import { defaultState } from '../utils/notification-reducer'; -import { NewsletterEmailPreview } from './NewsletterEmailPreview'; +import { NewsletterEmailPreview } from './HTMLPreview'; type StoryArgs = { notificationState: NotificationState; From b63052e7eb2ff97c4362e083d6d279fdd38d9234 Mon Sep 17 00:00:00 2001 From: DBlatcher Date: Thu, 17 Sep 2026 09:28:34 +0100 Subject: [PATCH 08/11] apply name changes to story --- .../NewsletterEmailPreview.stories.tsx | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/apps/frontend/src/preview/NewsletterEmailPreview.stories.tsx b/src/apps/frontend/src/preview/NewsletterEmailPreview.stories.tsx index c6b17665..2cbabf19 100644 --- a/src/apps/frontend/src/preview/NewsletterEmailPreview.stories.tsx +++ b/src/apps/frontend/src/preview/NewsletterEmailPreview.stories.tsx @@ -1,18 +1,18 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, waitFor, within } from 'storybook/test'; -import type { NotificationFormContextProps } from '../compose/NotificationContext'; +import type { NotificationFormContextProps } from '../compose/NotificationFormContext'; import { fetchFailError } from '../testing/api-fixtures'; import { articleFixture } from '../testing/capi-fixtures'; import { mockRequestEmailHtmlWithoutDelay } from '../testing/mock-fetch-email'; -import { WithNotificationContext } from '../testing/story-helpers'; -import type { NotificationState } from '../types'; -import type { NewsletterFormValues } from '../utils/notification-forms'; -import { defaultState } from '../utils/notification-reducer'; +import { useNotificationFormStory } from '../testing/useNotificationFormStory'; +import type { NotificationComposerState } from '../types'; +import { defaultAppAlertComposerState } from '../utils/notification-composer-reducer'; +import type { NewsletterEmailFormValues } from '../utils/notification-forms'; import { NewsletterEmailPreview } from './HTMLPreview'; type StoryArgs = { - notificationState: NotificationState; - formValues?: Partial; + notificationState: NotificationComposerState; + formValues?: Partial; functions?: Partial< Omit< NotificationFormContextProps, @@ -27,16 +27,17 @@ const meta: Meta = { title: 'Dispatch/Preview/NewsletterEmailPreview', component: NewsletterEmailPreview, args: { - notificationState: defaultState, + notificationState: defaultAppAlertComposerState, }, - render: ({ notificationState, formValues, functions }) => - WithNotificationContext( + render: function Render({ notificationState, functions, formValues }) { + return useNotificationFormStory( , notificationState, functions, - 'email', + 'newsletter', formValues, - ), + ); + }, }; export default meta; @@ -51,8 +52,8 @@ export const Default: Story = { export const WithContentNoAudience: Story = { args: { notificationState: { - ...defaultState, - content: articleFixture, + ...defaultAppAlertComposerState, + article: articleFixture, }, }, play: async ({ canvasElement }) => { @@ -68,8 +69,8 @@ export const WithContentNoAudience: Story = { export const WithContentAndAudience: Story = { args: { notificationState: { - ...defaultState, - content: articleFixture, + ...defaultAppAlertComposerState, + article: articleFixture, }, formValues: { audienceSegments: ['UK'], @@ -90,8 +91,8 @@ export const WithContentAndAudience: Story = { export const Loading: Story = { args: { notificationState: { - ...defaultState, - content: articleFixture, + ...defaultAppAlertComposerState, + article: articleFixture, }, formValues: { audienceSegments: ['UK'], @@ -104,8 +105,8 @@ export const Loading: Story = { export const FailedToLoad: Story = { args: { notificationState: { - ...defaultState, - content: articleFixture, + ...defaultAppAlertComposerState, + article: articleFixture, }, formValues: { audienceSegments: ['UK'], From e32fb224ed3d28d1c449a860dd1975769b6b0130 Mon Sep 17 00:00:00 2001 From: DBlatcher Date: Thu, 17 Sep 2026 10:07:01 +0100 Subject: [PATCH 09/11] update story test for new markup --- .../src/preview/NewsletterEmailPreviewSection.stories.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/apps/frontend/src/preview/NewsletterEmailPreviewSection.stories.tsx b/src/apps/frontend/src/preview/NewsletterEmailPreviewSection.stories.tsx index 1716f480..8e830597 100644 --- a/src/apps/frontend/src/preview/NewsletterEmailPreviewSection.stories.tsx +++ b/src/apps/frontend/src/preview/NewsletterEmailPreviewSection.stories.tsx @@ -137,11 +137,10 @@ export const PreviewTextToggleUpdatesHtmlAndTestEmail: Story = { requestPreviewTextTestEmail.mockClear(); const canvas = within(canvasElement); const toggle = canvas.getByRole('button', { name: 'Show preview text' }); - const preview = canvas.getByTitle('preview'); + const previewArticleElement = canvasElement.querySelector('figure article'); + const previewBodyText = () => - new DOMParser() - .parseFromString(preview.srcdoc, 'text/html') - .querySelector('h2 ~ div')?.textContent; + previewArticleElement?.querySelector('h2 ~ div')?.textContent; await waitFor(() => expect(previewBodyText()).toBe('Saved preview text')); await userEvent.type( From b6c49c84cb2b110eac3b7b202d513227a81e251a Mon Sep 17 00:00:00 2001 From: DBlatcher Date: Thu, 17 Sep 2026 16:20:41 +0100 Subject: [PATCH 10/11] only trigger effect to set innerHtml when preview.html changes --- src/apps/frontend/src/preview/HTMLPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/frontend/src/preview/HTMLPreview.tsx b/src/apps/frontend/src/preview/HTMLPreview.tsx index 495688ad..887d7f91 100644 --- a/src/apps/frontend/src/preview/HTMLPreview.tsx +++ b/src/apps/frontend/src/preview/HTMLPreview.tsx @@ -92,7 +92,7 @@ export const NewsletterEmailPreview = () => { if (preview?.html) { modifyContent(articleElement, parameters); } - }, [previewContainerElement, preview, parameters]); + }, [previewContainerElement, preview?.html, parameters]); useEffect(() => { const articleElement = previewContainerElement?.querySelector('article'); From a1fc1a8e609cbdad339965276a24adab1ae37037 Mon Sep 17 00:00:00 2001 From: DBlatcher Date: Thu, 17 Sep 2026 16:27:07 +0100 Subject: [PATCH 11/11] remove redundant stories --- .../src/preview/NewsletterEmailPreviewSection.stories.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/apps/frontend/src/preview/NewsletterEmailPreviewSection.stories.tsx b/src/apps/frontend/src/preview/NewsletterEmailPreviewSection.stories.tsx index 8e830597..ffd99222 100644 --- a/src/apps/frontend/src/preview/NewsletterEmailPreviewSection.stories.tsx +++ b/src/apps/frontend/src/preview/NewsletterEmailPreviewSection.stories.tsx @@ -62,10 +62,6 @@ export const Empty: Story = { }, }; -export const WithChannel: Story = {}; - -export const WithDeliveryTiming: Story = {}; - export const WithSegments: Story = { args: { composerState: populatedNewsletterEmailComposerState,