From 2f0045fb1588408c40881c2bce7094d476ab134a Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Fri, 10 Jan 2025 16:59:21 +0000 Subject: [PATCH] ntp: support multiple release-note lists --- .../app/entry-points/updateNotification.js | 8 ++- .../pages/new-tab/app/mock-transport.js | 10 ++-- .../components/UpdateNotification.js | 56 +++++++++++++------ .../components/UpdateNotification.module.css | 12 ++-- .../update-notification.spec.js | 17 ++++++ .../mocks/update-notification.data.js | 14 +++++ 6 files changed, 89 insertions(+), 28 deletions(-) diff --git a/special-pages/pages/new-tab/app/entry-points/updateNotification.js b/special-pages/pages/new-tab/app/entry-points/updateNotification.js index 4824f8d05..64c197573 100644 --- a/special-pages/pages/new-tab/app/entry-points/updateNotification.js +++ b/special-pages/pages/new-tab/app/entry-points/updateNotification.js @@ -4,8 +4,10 @@ import { UpdateNotificationProvider } from '../update-notification/UpdateNotific export function factory() { return ( - - - +
+ + + +
); } diff --git a/special-pages/pages/new-tab/app/mock-transport.js b/special-pages/pages/new-tab/app/mock-transport.js index 279f7e545..9ee8fb865 100644 --- a/special-pages/pages/new-tab/app/mock-transport.js +++ b/special-pages/pages/new-tab/app/mock-transport.js @@ -490,11 +490,11 @@ export function mockTransport() { let updateNotification = { content: null }; const isDelayed = url.searchParams.has('update-notification-delay'); - if (!isDelayed && url.searchParams.get('update-notification') === 'empty') { - updateNotification = updateNotificationExamples.empty; - } - if (!isDelayed && url.searchParams.get('update-notification') === 'populated') { - updateNotification = updateNotificationExamples.populated; + if (!isDelayed && url.searchParams.has('update-notification')) { + const value = url.searchParams.get('update-notification'); + if (value && value in updateNotificationExamples) { + updateNotification = updateNotificationExamples[value]; + } } /** @type {import('../types/new-tab.ts').InitialSetupResponse} */ diff --git a/special-pages/pages/new-tab/app/update-notification/components/UpdateNotification.js b/special-pages/pages/new-tab/app/update-notification/components/UpdateNotification.js index 5d47d7ed6..2cfaa1f9b 100644 --- a/special-pages/pages/new-tab/app/update-notification/components/UpdateNotification.js +++ b/special-pages/pages/new-tab/app/update-notification/components/UpdateNotification.js @@ -1,4 +1,4 @@ -import { h } from 'preact'; +import { Fragment, h } from 'preact'; import cn from 'classnames'; import styles from './UpdateNotification.module.css'; import { useContext, useId, useRef } from 'preact/hooks'; @@ -25,6 +25,11 @@ export function UpdateNotification({ notes, dismiss, version }) { ); } +/** + * @param {object} props + * @param {string[]} props.notes + * @param {string} props.version + */ export function WithNotes({ notes, version }) { const id = useId(); const ref = useRef(/** @type {HTMLDetailsElement|null} */ (null)); @@ -45,27 +50,46 @@ export function WithNotes({ notes, version }) { }} /> ); + /** @type {{title: string, notes:string[]}[]} */ + const chunks = [{ title: '', notes: [] }]; + let index = 0; + + for (const note of notes) { + const trimmed = note.trim(); + if (!trimmed) continue; + if (trimmed.startsWith('•')) { + /** + * Note: Doing this here as a very specific 'view' concern + * Note: using the `if` + `.slice` to avoid regex + * Note: `.slice` is safe on `•` because it is a single Unicode character + * and is represented by a single UTF-16 code unit. + */ + const bullet = trimmed.slice(1).trim(); + chunks[index].notes.push(bullet); + } else { + chunks.push({ title: trimmed, notes: [] }); + index += 1; + } + } + return (
{t('updateNotification_updated_version', { version })} {inlineLink}
-
    - {notes.map((note, index) => { - /** - * Note: Doing this here as a very specific 'view' concern - * Note: using the `if` + `.slice` to avoid regex - * Note: `.slice` is safe on `•` because it is a single Unicode character - * and is represented by a single UTF-16 code unit. - */ - let trimmed = note.trim(); - if (trimmed.startsWith('•')) { - trimmed = trimmed.slice(1).trim(); - } - return
  • {trimmed}
  • ; - })} -
+ {chunks.map((chunk, index) => { + return ( + + {chunk.title &&

{chunk.title}

} +
    + {chunk.notes.map((note, index) => { + return
  • {note}
  • ; + })} +
+
+ ); + })}
); diff --git a/special-pages/pages/new-tab/app/update-notification/components/UpdateNotification.module.css b/special-pages/pages/new-tab/app/update-notification/components/UpdateNotification.module.css index 0caf8c1a4..b1b1b254e 100644 --- a/special-pages/pages/new-tab/app/update-notification/components/UpdateNotification.module.css +++ b/special-pages/pages/new-tab/app/update-notification/components/UpdateNotification.module.css @@ -56,13 +56,17 @@ } .detailsContent { - padding-inline: var(--sp-2); - margin-top: var(--sp-2); + margin-top: var(--sp-4); text-align: left; + max-width: 380px; /* just a value that looks below it's centered heading */ + margin-left: auto; + margin-right: auto; +} +.title { + padding: 0.5rem 0; } - .list { - margin-left: var(--sp-20); + margin-left: 1.5rem; li { list-style: disc } diff --git a/special-pages/pages/new-tab/app/update-notification/integration-tests/update-notification.spec.js b/special-pages/pages/new-tab/app/update-notification/integration-tests/update-notification.spec.js index a7a15d0a8..b4f01f146 100644 --- a/special-pages/pages/new-tab/app/update-notification/integration-tests/update-notification.spec.js +++ b/special-pages/pages/new-tab/app/update-notification/integration-tests/update-notification.spec.js @@ -33,4 +33,21 @@ test.describe('newtab update notifications', () => { await page.getByRole('button', { name: 'Dismiss' }).click(); await ntp.mocks.waitForCallCount({ method: 'updateNotification_dismiss', count: 1 }); }); + test('handles multiple lists', async ({ page }, workerInfo) => { + const ntp = NewtabPage.create(page, workerInfo); + await ntp.reducedMotion(); + await ntp.openPage({ updateNotification: 'multipleSections' }); + await page.getByRole('link', { name: "what's new" }).click(); + await expect(page.locator('[data-entry-point="updateNotification"]')).toMatchAriaSnapshot(` + - group: + - list: + - listitem: We're excited to introduce a new browsing feature - Fire Windows. These special windows work the same way as normal windows, except they isolate your activity from other browsing data and self-destruct when closed. This means you can use a Fire Window to browse without saving local history or to sign into a site with a different account. You can open a new Fire Window anytime from the Fire Button menu. + - listitem: Try the new bookmark management view that opens in a tab for more robust bookmark organization. + - paragraph: For Privacy Pro subscribers + - list: + - listitem: VPN notifications are now available to help communicate VPN status. + - listitem: Some apps aren't compatible with VPNs. You can now exclude these apps to use them while connected to the VPN. + - listitem: Visit https://duckduckgo.com/pro for more information. + `); + }); }); diff --git a/special-pages/pages/new-tab/app/update-notification/mocks/update-notification.data.js b/special-pages/pages/new-tab/app/update-notification/mocks/update-notification.data.js index d959b5f73..d6f28ebc3 100644 --- a/special-pages/pages/new-tab/app/update-notification/mocks/update-notification.data.js +++ b/special-pages/pages/new-tab/app/update-notification/mocks/update-notification.data.js @@ -18,4 +18,18 @@ export const updateNotificationExamples = { version: '1.91', }, }, + multipleSections: { + content: { + // prettier-ignore + notes: [ + `• We're excited to introduce a new browsing feature - Fire Windows. These special windows work the same way as normal windows, except they isolate your activity from other browsing data and self-destruct when closed. This means you can use a Fire Window to browse without saving local history or to sign into a site with a different account. You can open a new Fire Window anytime from the Fire Button menu.`, + `• Try the new bookmark management view that opens in a tab for more robust bookmark organization.`, + `For Privacy Pro subscribers`, + `• VPN notifications are now available to help communicate VPN status.`, + `• Some apps aren't compatible with VPNs. You can now exclude these apps to use them while connected to the VPN.`, + `• Visit https://duckduckgo.com/pro for more information.`, + ], + version: '0.98.4', + }, + }, };